[PATCH] Add __is_nothrow_convertible to fix std::is_nothrow_invocable_r

Jonathan Wakely jwakely@redhat.com
Tue May 14 14:40:00 GMT 2019


The definition of is_nothrow_invocable in terms of is_convertible and
is_nothrow_constructible is incorrect, because a type could have an
explicit constructor that means is_nothrow_constructible is true, but
implicit conversions could use a different constructor that is
potentially-throwing.

Fix it by adding a C++11 version of C++20's is_nothrow_convertible that
only considers implicit conversions.

	* include/std/type_traits (__is_nt_convertible_helper): Define it
	unconditionally, not only for C++20.
	(__is_nothrow_convertible): Define internal trait for use in C++11.
	(__is_nt_invocable_impl: Fix by using __is_nothrow_convertible.
	(is_invocable_r_v, is_nothrow_invocable_r_v): Add missing parameter.
	* testsuite/20_util/is_nothrow_convertible/value_ext.cc: New test.
	* testsuite/20_util/is_nothrow_convertible/value.cc: Check with type
	that has nothrow explicit conversion but potentially-throwing implicit
	conversion.
	* testsuite/20_util/is_nothrow_invocable/value.cc: Likewise.
	* testsuite/20_util/is_nothrow_invocable/value_ext.cc: Fix helper
	function to only consider implicit conversions.
	* testsuite/20_util/tuple/cons/noexcept_specs.cc: Add comment.

Tested powerpc64le-linux, committed to trunk.

-------------- next part --------------
commit e872e963111c865ac5a446703663bdc52990b938
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Tue May 14 14:21:53 2019 +0100

    Add __is_nothrow_convertible to fix std::is_nothrow_invocable_r
    
    The definition of is_nothrow_invocable in terms of is_convertible and
    is_nothrow_constructible is incorrect, because a type could have an
    explicit constructor that means is_nothrow_constructible is true, but
    implicit conversions could use a different constructor that is
    potentially-throwing.
    
    Fix it by adding a C++11 version of C++20's is_nothrow_convertible that
    only considers implicit conversions.
    
            * include/std/type_traits (__is_nt_convertible_helper): Define it
            unconditionally, not only for C++20.
            (__is_nothrow_convertible): Define internal trait for use in C++11.
            (__is_nt_invocable_impl: Fix by using __is_nothrow_convertible.
            (is_invocable_r_v, is_nothrow_invocable_r_v): Add missing parameter.
            * testsuite/20_util/is_nothrow_convertible/value_ext.cc: New test.
            * testsuite/20_util/is_nothrow_convertible/value.cc: Check with type
            that has nothrow explicit conversion but potentially-throwing implicit
            conversion.
            * testsuite/20_util/is_nothrow_invocable/value.cc: Likewise.
            * testsuite/20_util/is_nothrow_invocable/value_ext.cc: Fix helper
            function to only consider implicit conversions.
            * testsuite/20_util/tuple/cons/noexcept_specs.cc: Add comment.

diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index ea733e7b7b2..f68d366269d 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -1378,7 +1378,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     : public __is_convertible_helper<_From, _To>::type
     { };
 
-#if __cplusplus > 201703L
     template<typename _From, typename _To,
            bool = __or_<is_void<_From>, is_function<_To>,
                         is_array<_To>>::value>
@@ -1393,7 +1392,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	static void __test_aux(_To1) noexcept;
 
       template<typename _From1, typename _To1>
-	static bool_constant<noexcept(__test_aux<_To1>(std::declval<_From1>()))>
+	static
+	__bool_constant<noexcept(__test_aux<_To1>(std::declval<_From1>()))>
 	__test(int);
 
       template<typename, typename>
@@ -1404,6 +1404,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       using type = decltype(__test<_From, _To>(0));
     };
 
+  // is_nothrow_convertible for C++11
+  template<typename _From, typename _To>
+    struct __is_nothrow_convertible
+    : public __is_nt_convertible_helper<_From, _To>::type
+    { };
+
+#if __cplusplus > 201703L
   /// is_nothrow_convertible
   template<typename _From, typename _To>
     struct is_nothrow_convertible
@@ -2831,8 +2838,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     struct __is_nt_invocable_impl<_Result, _Ret,
 				  __void_t<typename _Result::type>>
     : __or_<is_void<_Ret>,
-	    __and_<is_convertible<typename _Result::type, _Ret>,
-		   is_nothrow_constructible<_Ret, typename _Result::type>>>
+	    __is_nothrow_convertible<typename _Result::type, _Ret>>
     { };
 
   /// std::is_nothrow_invocable_r
@@ -2852,14 +2858,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       = is_nothrow_invocable<_Fn, _Args...>::value;
 
   /// std::is_invocable_r_v
-  template<typename _Fn, typename... _Args>
+  template<typename _Ret, typename _Fn, typename... _Args>
     inline constexpr bool is_invocable_r_v
-      = is_invocable_r<_Fn, _Args...>::value;
+      = is_invocable_r<_Ret, _Fn, _Args...>::value;
 
   /// std::is_nothrow_invocable_r_v
-  template<typename _Fn, typename... _Args>
+  template<typename _Ret, typename _Fn, typename... _Args>
     inline constexpr bool is_nothrow_invocable_r_v
-      = is_nothrow_invocable_r<_Fn, _Args...>::value;
+      = is_nothrow_invocable_r<_Ret, _Fn, _Args...>::value;
 #endif // C++17
 
 #if __cplusplus >= 201703L
diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value.cc
index b82b68c23b9..c4c2fda845c 100644
--- a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value.cc
+++ b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value.cc
@@ -21,9 +21,12 @@
 #include <type_traits>
 #include <testsuite_tr1.h>
 
+#ifndef IS_NT_CONVERTIBLE_DEFINED
+using std::is_nothrow_convertible;
+#endif
+
 void test01()
 {
-  using std::is_nothrow_convertible;
   using namespace __gnu_test;
 
   // Positive conversion tests.
@@ -175,3 +178,16 @@ void test01()
 				  NoexceptMoveConsClass&,
 				  NoexceptMoveConsClass>(false));
 }
+
+void test02()
+{
+  struct X { };
+
+  struct Y
+  {
+    explicit Y(X) noexcept; // not viable for implicit conversions
+    Y(...);
+  };
+
+  static_assert(!is_nothrow_convertible<X, Y>::value, "");
+}
diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc
new file mode 100644
index 00000000000..50bab80596f
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/is_nothrow_convertible/value_ext.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do compile { target c++11 } }
+
+#include <type_traits>
+
+// Test the non-standard __is_nothrow_convertible trait
+
+template<typename From, typename To>
+  using is_nothrow_convertible = std::__is_nothrow_convertible<From, To>;
+
+#define IS_NT_CONVERTIBLE_DEFINED
+#include "value.cc"
diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value.cc
index 0a365a910fc..04d310fff38 100644
--- a/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value.cc
+++ b/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value.cc
@@ -16,7 +16,7 @@
 // <http://www.gnu.org/licenses/>.
 
 // { dg-options "-std=gnu++17" }
-// { dg-do compile }
+// { dg-do compile { target c++17 } }
 
 #include <type_traits>
 
@@ -151,4 +151,17 @@ void test01()
 		   "would call private member");
   static_assert( ! is_nt_invocable_r<void, F, int, int >(),
 		   "would call private member");
+
+  struct FX {
+    X operator()() const noexcept { return {}; }
+  };
+  static_assert( is_nt_invocable< FX >(), "FX::operator() is nothrow" );
+  static_assert( is_nt_invocable_r<X, FX >(), "no conversion needed" );
+
+  struct Y {
+    explicit Y(X) noexcept; // not viable for implicit conversions
+    Y(...);
+  };
+
+  static_assert( ! is_nt_invocable_r<Y, FX >(), "conversion to Y can throw" );
 }
diff --git a/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value_ext.cc b/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value_ext.cc
index e9a4de66de7..3a133ade4de 100644
--- a/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value_ext.cc
+++ b/libstdc++-v3/testsuite/20_util/is_nothrow_invocable/value_ext.cc
@@ -23,13 +23,20 @@ template<typename... T>
   constexpr bool is_nt_invocable()
   { return std::__is_nothrow_invocable<T...>::value; }
 
-  template<typename R, typename... T>
+template<typename R, typename... T>
   constexpr bool is_nt_invocable_conv(std::true_type)
   {
     using result_type = typename std::__invoke_result<T...>::type;
+
+    struct ConvIsNothrow
+    {
+      static void test(std::true_type, R) noexcept;
+      static void test(std::false_type, const result_type&);
+    };
+
     return std::is_void<R>::value
-      || (std::is_convertible<result_type, R>::value
-	  && std::is_nothrow_constructible<R, result_type>::value);
+      || noexcept(ConvIsNothrow::test(std::is_convertible<result_type, R>(),
+				      std::declval<result_type>()));
   }
 
 template<typename R, typename... T>
diff --git a/libstdc++-v3/testsuite/20_util/tuple/cons/noexcept_specs.cc b/libstdc++-v3/testsuite/20_util/tuple/cons/noexcept_specs.cc
index da9ef1c26b2..d04b0aee8ed 100644
--- a/libstdc++-v3/testsuite/20_util/tuple/cons/noexcept_specs.cc
+++ b/libstdc++-v3/testsuite/20_util/tuple/cons/noexcept_specs.cc
@@ -56,7 +56,9 @@ namespace test_trait{
       using type = decltype(test<From, To>(0));
     };
 
-  /// is_nothrow_convertible
+  // Similar to std::is_nothrow_convertible, but only considers whether the
+  // actual conversion can throw (and not any potential copies of From).
+  // This means the result is not affected by copy elision of From in C++17.
   template<typename From, typename To>
     struct is_nothrow_convertible
     : public is_nt_convertible_helper<From, To>::type


More information about the Gcc-patches mailing list