[libcxx-commits] [libcxx] c3a2488 - [libc++] type_traits: use __is_core_convertible in __invokable_r.

Nikolas Klauser via libcxx-commits libcxx-commits at lists.llvm.org
Tue May 24 01:24:11 PDT 2022


Author: Aaron Jacobs
Date: 2022-05-24T10:22:40+02:00
New Revision: c3a24882903df5b25c011628162e82e47401f71f

URL: https://github.com/llvm/llvm-project/commit/c3a24882903df5b25c011628162e82e47401f71f
DIFF: https://github.com/llvm/llvm-project/commit/c3a24882903df5b25c011628162e82e47401f71f.diff

LOG: [libc++] type_traits: use __is_core_convertible in __invokable_r.

This fixes incorrect handling of non-moveable types, adding tests for this case.
See [issue 55346](https://github.com/llvm/llvm-project/issues/55346).

The current implementation is based on is_convertible, which is
[defined](https://timsong-cpp.github.io/cppwp/n4659/meta.rel#5) in terms of
validity of the following function:

```
To test() {
  return declval<From>();
}
```

But this doesn't work if To and From are both some non-moveable type, which the
[definition](https://timsong-cpp.github.io/cppwp/n4659/conv#3) of implicit
conversions says should work due to guaranteed copy elision:

```
To to = E;  // E has type From
```

It is this latter definition that is used in the
[definition](https://timsong-cpp.github.io/cppwp/n4659/function.objects#func.require-2)
of INVOKE<R>. Make __invokable_r use __is_core_convertible, which
captures the ability to use guaranteed copy elision, making the
definition correct for non-moveable types.

Fixes llvm/llvm-project#55346.

Reviewed By: #libc, philnik, EricWF

Spies: EricWF, jloser, ldionne, philnik, libcxx-commits

Differential Revision: https://reviews.llvm.org/D125300

Added: 
    libcxx/test/std/utilities/meta/meta.rel/is_invocable_r.compile.pass.cpp
    libcxx/test/std/utilities/meta/meta.rel/is_invocable_r_v.compile.pass.cpp

Modified: 
    libcxx/include/type_traits
    libcxx/test/std/utilities/meta/meta.rel/is_nothrow_invocable.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/include/type_traits b/libcxx/include/type_traits
index b56f5395dd261..5908850cba5ed 100644
--- a/libcxx/include/type_traits
+++ b/libcxx/include/type_traits
@@ -2992,16 +2992,10 @@ struct __invokable_r
   // or incomplete array types as required by the standard.
   using _Result = decltype(__try_call<_Fp, _Args...>(0));
 
-  using type =
-  typename conditional<
+  using type = typename conditional<
       _IsNotSame<_Result, __nat>::value,
-      typename conditional<
-          is_void<_Ret>::value,
-          true_type,
-          is_convertible<_Result, _Ret>
-      >::type,
-      false_type
-  >::type;
+      typename conditional< is_void<_Ret>::value, true_type, __is_core_convertible<_Result, _Ret> >::type,
+      false_type >::type;
   static const bool value = type::value;
 };
 template <class _Fp, class ..._Args>

diff  --git a/libcxx/test/std/utilities/meta/meta.rel/is_invocable_r.compile.pass.cpp b/libcxx/test/std/utilities/meta/meta.rel/is_invocable_r.compile.pass.cpp
new file mode 100644
index 0000000000000..778b24d99e91e
--- /dev/null
+++ b/libcxx/test/std/utilities/meta/meta.rel/is_invocable_r.compile.pass.cpp
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14
+
+// is_invocable_r
+
+#include <type_traits>
+
+// Non-invocable types
+
+static_assert(!std::is_invocable_r<void, void>::value);
+static_assert(!std::is_invocable_r<void, int>::value);
+static_assert(!std::is_invocable_r<void, int*>::value);
+static_assert(!std::is_invocable_r<void, int&>::value);
+static_assert(!std::is_invocable_r<void, int&&>::value);
+
+// Result type matches
+
+template <typename T>
+T Return();
+
+static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
+static_assert(std::is_invocable_r<char, decltype(Return<char>)>::value);
+static_assert(std::is_invocable_r<int*, decltype(Return<int*>)>::value);
+static_assert(std::is_invocable_r<int&, decltype(Return<int&>)>::value);
+static_assert(std::is_invocable_r<int&&, decltype(Return<int&&>)>::value);
+
+// void result type
+
+// Any actual return type should be useable with a result type of void.
+static_assert(std::is_invocable_r<void, decltype(Return<void>)>::value);
+static_assert(std::is_invocable_r<void, decltype(Return<int>)>::value);
+static_assert(std::is_invocable_r<void, decltype(Return<int*>)>::value);
+static_assert(std::is_invocable_r<void, decltype(Return<int&>)>::value);
+static_assert(std::is_invocable_r<void, decltype(Return<int&&>)>::value);
+
+// const- and volatile-qualified void should work too.
+static_assert(std::is_invocable_r<const void, decltype(Return<void>)>::value);
+static_assert(std::is_invocable_r<const void, decltype(Return<int>)>::value);
+static_assert(std::is_invocable_r<volatile void, decltype(Return<void>)>::value);
+static_assert(std::is_invocable_r<volatile void, decltype(Return<int>)>::value);
+static_assert(std::is_invocable_r<const volatile void, decltype(Return<void>)>::value);
+static_assert(std::is_invocable_r<const volatile void, decltype(Return<int>)>::value);
+
+// Conversion of result type
+
+// It should be possible to use a result type to which the actual return type
+// can be converted.
+static_assert(std::is_invocable_r<char, decltype(Return<int>)>::value);
+static_assert(std::is_invocable_r<const int*, decltype(Return<int*>)>::value);
+static_assert(std::is_invocable_r<void*, decltype(Return<int*>)>::value);
+static_assert(std::is_invocable_r<const int&, decltype(Return<int>)>::value);
+static_assert(std::is_invocable_r<const int&, decltype(Return<int&>)>::value);
+static_assert(std::is_invocable_r<const int&, decltype(Return<int&&>)>::value);
+static_assert(std::is_invocable_r<const char&, decltype(Return<int>)>::value);
+
+// But not a result type where the conversion doesn't work.
+static_assert(!std::is_invocable_r<int, decltype(Return<void>)>::value);
+static_assert(!std::is_invocable_r<int, decltype(Return<int*>)>::value);
+
+// Non-moveable result type
+
+// Define a type that can't be move-constructed.
+struct CantMove {
+  CantMove() = default;
+  CantMove(CantMove&&) = delete;
+};
+
+static_assert(!std::is_move_constructible_v<CantMove>);
+static_assert(!std::is_copy_constructible_v<CantMove>);
+
+// Define functions that return that type.
+CantMove MakeCantMove() { return {}; }
+CantMove MakeCantMoveWithArg(int) { return {}; }
+
+// Assumption check: it should be possible to call one of those functions and
+// use it to initialize a CantMove object.
+CantMove cant_move = MakeCantMove();
+
+// Therefore std::is_invocable_r should agree that they can be invoked to yield
+// a CantMove.
+static_assert(std::is_invocable_r<CantMove, decltype(MakeCantMove)>::value);
+static_assert(std::is_invocable_r<CantMove, decltype(MakeCantMoveWithArg), int>::value);
+
+// Of course it still shouldn't be possible to call one of the functions and get
+// back some other type.
+static_assert(!std::is_invocable_r<int, decltype(MakeCantMove)>::value);
+
+// And the argument types should still be important.
+static_assert(!std::is_invocable_r<CantMove, decltype(MakeCantMove), int>::value);
+static_assert(!std::is_invocable_r<CantMove, decltype(MakeCantMoveWithArg)>::value);
+
+// is_invocable_r
+
+// The struct form should be available too, not just the _v variant.
+static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
+static_assert(!std::is_invocable_r<int*, decltype(Return<int>)>::value);

diff  --git a/libcxx/test/std/utilities/meta/meta.rel/is_invocable_r_v.compile.pass.cpp b/libcxx/test/std/utilities/meta/meta.rel/is_invocable_r_v.compile.pass.cpp
new file mode 100644
index 0000000000000..32530f2c20865
--- /dev/null
+++ b/libcxx/test/std/utilities/meta/meta.rel/is_invocable_r_v.compile.pass.cpp
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14
+
+// is_invocable_r
+
+#include <type_traits>
+
+// Non-invocable types
+
+static_assert(!std::is_invocable_r_v<void, void>);
+static_assert(!std::is_invocable_r_v<void, int>);
+static_assert(!std::is_invocable_r_v<void, int*>);
+static_assert(!std::is_invocable_r_v<void, int&>);
+static_assert(!std::is_invocable_r_v<void, int&&>);
+
+// Result type matches
+
+template <typename T>
+T Return();
+
+static_assert(std::is_invocable_r_v<int, decltype(Return<int>)>);
+static_assert(std::is_invocable_r_v<char, decltype(Return<char>)>);
+static_assert(std::is_invocable_r_v<int*, decltype(Return<int*>)>);
+static_assert(std::is_invocable_r_v<int&, decltype(Return<int&>)>);
+static_assert(std::is_invocable_r_v<int&&, decltype(Return<int&&>)>);
+
+// void result type
+
+// Any actual return type should be useable with a result type of void.
+static_assert(std::is_invocable_r_v<void, decltype(Return<void>)>);
+static_assert(std::is_invocable_r_v<void, decltype(Return<int>)>);
+static_assert(std::is_invocable_r_v<void, decltype(Return<int*>)>);
+static_assert(std::is_invocable_r_v<void, decltype(Return<int&>)>);
+static_assert(std::is_invocable_r_v<void, decltype(Return<int&&>)>);
+
+// const- and volatile-qualified void should work too.
+static_assert(std::is_invocable_r_v<const void, decltype(Return<void>)>);
+static_assert(std::is_invocable_r_v<const void, decltype(Return<int>)>);
+static_assert(std::is_invocable_r_v<volatile void, decltype(Return<void>)>);
+static_assert(std::is_invocable_r_v<volatile void, decltype(Return<int>)>);
+static_assert(std::is_invocable_r_v<const volatile void, decltype(Return<void>)>);
+static_assert(std::is_invocable_r_v<const volatile void, decltype(Return<int>)>);
+
+// Conversion of result type
+
+// It should be possible to use a result type to which the actual return type
+// can be converted.
+static_assert(std::is_invocable_r_v<char, decltype(Return<int>)>);
+static_assert(std::is_invocable_r_v<const int*, decltype(Return<int*>)>);
+static_assert(std::is_invocable_r_v<void*, decltype(Return<int*>)>);
+static_assert(std::is_invocable_r_v<const int&, decltype(Return<int>)>);
+static_assert(std::is_invocable_r_v<const int&, decltype(Return<int&>)>);
+static_assert(std::is_invocable_r_v<const int&, decltype(Return<int&&>)>);
+static_assert(std::is_invocable_r_v<const char&, decltype(Return<int>)>);
+
+// But not a result type where the conversion doesn't work.
+static_assert(!std::is_invocable_r_v<int, decltype(Return<void>)>);
+static_assert(!std::is_invocable_r_v<int, decltype(Return<int*>)>);
+
+// Non-moveable result type
+
+// Define a type that can't be move-constructed.
+struct CantMove {
+  CantMove() = default;
+  CantMove(CantMove&&) = delete;
+};
+
+static_assert(!std::is_move_constructible_v<CantMove>);
+static_assert(!std::is_copy_constructible_v<CantMove>);
+
+// Define functions that return that type.
+CantMove MakeCantMove() { return {}; }
+CantMove MakeCantMoveWithArg(int) { return {}; }
+
+// Assumption check: it should be possible to call one of those functions and
+// use it to initialize a CantMove object.
+CantMove cant_move = MakeCantMove();
+
+// Therefore std::is_invocable_r should agree that they can be invoked to yield
+// a CantMove.
+static_assert(std::is_invocable_r_v<CantMove, decltype(MakeCantMove)>);
+static_assert(std::is_invocable_r_v<CantMove, decltype(MakeCantMoveWithArg), int>);
+
+// Of course it still shouldn't be possible to call one of the functions and get
+// back some other type.
+static_assert(!std::is_invocable_r_v<int, decltype(MakeCantMove)>);
+
+// And the argument types should still be important.
+static_assert(!std::is_invocable_r_v<CantMove, decltype(MakeCantMove), int>);
+static_assert(!std::is_invocable_r_v<CantMove, decltype(MakeCantMoveWithArg)>);
+
+// is_invocable_r
+
+// The struct form should be available too, not just the _v variant.
+static_assert(std::is_invocable_r<int, decltype(Return<int>)>::value);
+static_assert(!std::is_invocable_r<int*, decltype(Return<int>)>::value);

diff  --git a/libcxx/test/std/utilities/meta/meta.rel/is_nothrow_invocable.pass.cpp b/libcxx/test/std/utilities/meta/meta.rel/is_nothrow_invocable.pass.cpp
index 63ecc202ca83d..fa6048e869e18 100644
--- a/libcxx/test/std/utilities/meta/meta.rel/is_nothrow_invocable.pass.cpp
+++ b/libcxx/test/std/utilities/meta/meta.rel/is_nothrow_invocable.pass.cpp
@@ -185,6 +185,24 @@ int main(int, char**) {
     static_assert(std::is_nothrow_invocable_r<Implicit, Fn, Tag&>::value, "");
     static_assert(throws_invocable_r<ThrowsImplicit, Fn, Tag&>(), "");
   }
+  {
+    // Check that it's fine if the result type is non-moveable.
+    struct CantMove {
+      CantMove() = default;
+      CantMove(CantMove&&) = delete;
+    };
+
+    static_assert(!std::is_move_constructible_v<CantMove>);
+    static_assert(!std::is_copy_constructible_v<CantMove>);
+
+    using Fn = CantMove() noexcept;
+
+    static_assert(std::is_nothrow_invocable_r<CantMove, Fn>::value);
+    static_assert(!std::is_nothrow_invocable_r<CantMove, Fn, int>::value);
+
+    static_assert(std::is_nothrow_invocable_r_v<CantMove, Fn>);
+    static_assert(!std::is_nothrow_invocable_r_v<CantMove, Fn, int>);
+  }
   {
     // Check for is_nothrow_invocable_v
     using Fn = CallObject<true, int>;


        


More information about the libcxx-commits mailing list