On 15/08/19 17:04 +0100, Jonathan Wakely wrote:
In C++17 a function can return a prvalue of a type that cannot be moved
or copied. The current implementation of std::is_invocable_r uses
std::is_convertible to test the conversion to R required by INVOKE<R>.
That fails for non-copyable prvalues, because std::is_convertible is
defined in terms of std::declval which uses std::add_rvalue_reference.
In C++17 conversion from R to R involves no copies and so is not the
same as conversion from R&& to R.

This commit changes std::is_invocable_r to check the conversion without
using std::is_convertible.

std::function also contains a similar check using std::is_convertible,
which can be fixed by simply reusing std::is_invocable_r (but because
std::is_invocable_r is not defined for C++11 it uses the underlying
std::__is_invocable_impl trait directly).

        PR libstdc++/91456
        * include/bits/std_function.h (__check_func_return_type): Remove.
        (function::_Callable): Use std::__is_invocable_impl instead of
        __check_func_return_type.
        * include/std/type_traits (__is_invocable_impl): Add another defaulted
        template parameter. Define a separate partial specialization for
        INVOKE and INVOKE<void>. For INVOKE<R> replace is_convertible check
        with a check that models delayed temporary materialization.
        * testsuite/20_util/function/91456.cc: New test.
        * testsuite/20_util/is_invocable/91456.cc: New test.

With some minor changes to __is_convertible_helper we could make that
usable by both std::is_convertible and __is_invokable_impl.

I don't plan to commit this now but might do at a later date.

diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits
index 44db2cade5d..4df3fee4c77 100644
--- a/libstdc++-v3/include/std/type_traits
+++ b/libstdc++-v3/include/std/type_traits
@@ -1491,20 +1491,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
            bool = __or_<is_void<_From>, is_function<_To>,
                         is_array<_To>>::value>
     struct __is_convertible_helper
-    {
-      typedef typename is_void<_To>::type type;
-    };
+    : public is_void<_To>::type
+    { };
 
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
   template<typename _From, typename _To>
     class __is_convertible_helper<_From, _To, false>
     {
+      // Unlike declval, this doesn't add_rvalue_reference.
+      template<typename _From1>
+	static _From1 __declval();
+
       template<typename _To1>
 	static void __test_aux(_To1) noexcept;
 
       template<typename _From1, typename _To1,
-	       typename = decltype(__test_aux<_To1>(std::declval<_From1>()))>
+	       typename = decltype(__test_aux<_To1>(__declval<_From1>()))>
 	static true_type
 	__test(int);
 
@@ -1513,14 +1516,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	__test(...);
 
     public:
-      typedef decltype(__test<_From, _To>(0)) type;
+      using type = decltype(__test<_From, _To>(0));
     };
 #pragma GCC diagnostic pop
 
+  template<typename _Tp> struct add_rvalue_reference;
+
   /// is_convertible
   template<typename _From, typename _To>
     struct is_convertible
-    : public __is_convertible_helper<_From, _To>::type
+    : public __is_convertible_helper<typename add_rvalue_reference<_From>::type,
+				     _To>::type
     { };
 
   template<typename _From, typename _To,
@@ -1535,12 +1541,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _From, typename _To>
     class __is_nt_convertible_helper<_From, _To, false>
     {
+      // Unlike declval, this doesn't add_rvalue_reference.
+      template<typename _From1>
+	static _From1 __declval();
+
       template<typename _To1>
 	static void __test_aux(_To1) noexcept;
 
       template<typename _From1, typename _To1>
 	static
-	__bool_constant<noexcept(__test_aux<_To1>(std::declval<_From1>()))>
+	__bool_constant<noexcept(__test_aux<_To1>(__declval<_From1>()))>
 	__test(int);
 
       template<typename, typename>
@@ -1555,14 +1565,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   // is_nothrow_convertible for C++11
   template<typename _From, typename _To>
     struct __is_nothrow_convertible
-    : public __is_nt_convertible_helper<_From, _To>::type
+    : __is_nt_convertible_helper<typename add_rvalue_reference<_From>::type,
+				 _To>::type
     { };
 
 #if __cplusplus > 201703L
   /// is_nothrow_convertible
   template<typename _From, typename _To>
     struct is_nothrow_convertible
-    : public __is_nt_convertible_helper<_From, _To>::type
+    : __is_nt_convertible_helper<typename add_rvalue_reference<_From>::type,
+				 _To>::type
     { };
 
   /// is_nothrow_convertible_v
@@ -2896,35 +2908,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     : true_type
     { };
 
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wctor-dtor-privacy"
   // Used for INVOKE<R> expressions to check the implicit conversion to R.
   template<typename _Result, typename _Ret>
     struct __is_invocable_impl<_Result, _Ret,
 			       /* is_void<_Ret> = */ false,
 			       __void_t<typename _Result::type>>
-    {
-    private:
-      // The type of the INVOKE expression.
-      // Unlike declval, this doesn't add_rvalue_reference.
-      static typename _Result::type _S_get();
-
-      template<typename _Tp>
-	static void _S_conv(_Tp);
-
-      // This overload is viable if INVOKE(f, args...) can convert to _Tp.
-      template<typename _Tp, typename = decltype(_S_conv<_Tp>(_S_get()))>
-	static true_type
-	_S_test(int);
-
-      template<typename _Tp>
-	static false_type
-	_S_test(...);
-
-    public:
-      using type = decltype(_S_test<_Ret>(1));
-    };
-#pragma GCC diagnostic pop
+    : __is_convertible_helper<typename _Result::type, _Ret>
+    { };
 
   template<typename _Fn, typename... _ArgTypes>
     struct __is_invocable

Reply via email to