gmock-actions: properly support non-moveable results in is_callable_r
.
Previously this excluded callables that return non-moveable types. This is the same as the [libc++ std::is_invocable_r bug](https://github.com/llvm/llvm-project/issues/55346) fixed by [this commit](https://github.com/llvm/llvm-project/commit/c3a24882903d): it's wrong to use std::is_convertible for checking the return type, since (despite its name) that doesn't check the standard-defined notion of "implicitly convertible". Instead we must base the check on whether the source type can be used as an argument to a function that accepts the destination type. PiperOrigin-RevId: 451341205 Change-Id: I2530051312a0361ea7a2ce26993ae973c9242089
This commit is contained in:
parent
56246cdb94
commit
28356773cb
@ -298,6 +298,53 @@ struct disjunction<P1, Ps...>
|
|||||||
template <typename...>
|
template <typename...>
|
||||||
using void_t = void;
|
using void_t = void;
|
||||||
|
|
||||||
|
// Detects whether an expression of type `From` can be implicitly converted to
|
||||||
|
// `To` according to [conv]. In C++17, [conv]/3 defines this as follows:
|
||||||
|
//
|
||||||
|
// An expression e can be implicitly converted to a type T if and only if
|
||||||
|
// the declaration T t=e; is well-formed, for some invented temporary
|
||||||
|
// variable t ([dcl.init]).
|
||||||
|
//
|
||||||
|
// [conv]/2 implies we can use function argument passing to detect whether this
|
||||||
|
// initialization is valid.
|
||||||
|
//
|
||||||
|
// Note that this is distinct from is_convertible, which requires this be valid:
|
||||||
|
//
|
||||||
|
// To test() {
|
||||||
|
// return declval<From>();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In particular, is_convertible doesn't give the correct answer when `To` and
|
||||||
|
// `From` are the same non-moveable type since `declval<From>` will be an rvalue
|
||||||
|
// reference, defeating the guaranteed copy elision that would otherwise make
|
||||||
|
// this function work.
|
||||||
|
//
|
||||||
|
// REQUIRES: `From` is not cv void.
|
||||||
|
template <typename From, typename To>
|
||||||
|
struct is_implicitly_convertible {
|
||||||
|
private:
|
||||||
|
// A function that accepts a parameter of type T. This can be called with type
|
||||||
|
// U successfully only if U is implicitly convertible to T.
|
||||||
|
template <typename T>
|
||||||
|
static void Accept(T);
|
||||||
|
|
||||||
|
// A function that creates a value of type T.
|
||||||
|
template <typename T>
|
||||||
|
static T Make();
|
||||||
|
|
||||||
|
// An overload be selected when implicit conversion from T to To is possible.
|
||||||
|
template <typename T, typename = decltype(Accept<To>(Make<T>()))>
|
||||||
|
static std::true_type TestImplicitConversion(int);
|
||||||
|
|
||||||
|
// A fallback overload selected in all other cases.
|
||||||
|
template <typename T>
|
||||||
|
static std::false_type TestImplicitConversion(...);
|
||||||
|
|
||||||
|
public:
|
||||||
|
using type = decltype(TestImplicitConversion<From>(0));
|
||||||
|
static constexpr bool value = type::value;
|
||||||
|
};
|
||||||
|
|
||||||
// Like std::invoke_result_t from C++17, but works only for objects with call
|
// Like std::invoke_result_t from C++17, but works only for objects with call
|
||||||
// operators (not e.g. member function pointers, which we don't need specific
|
// operators (not e.g. member function pointers, which we don't need specific
|
||||||
// support for in OnceAction because std::function deals with them).
|
// support for in OnceAction because std::function deals with them).
|
||||||
@ -313,9 +360,9 @@ struct is_callable_r_impl : std::false_type {};
|
|||||||
template <typename R, typename F, typename... Args>
|
template <typename R, typename F, typename... Args>
|
||||||
struct is_callable_r_impl<void_t<call_result_t<F, Args...>>, R, F, Args...>
|
struct is_callable_r_impl<void_t<call_result_t<F, Args...>>, R, F, Args...>
|
||||||
: std::conditional<
|
: std::conditional<
|
||||||
std::is_same<R, void>::value, //
|
std::is_void<R>::value, //
|
||||||
std::true_type, //
|
std::true_type, //
|
||||||
std::is_convertible<call_result_t<F, Args...>, R>>::type {};
|
is_implicitly_convertible<call_result_t<F, Args...>, R>>::type {};
|
||||||
|
|
||||||
// Like std::is_invocable_r from C++17, but works only for objects with call
|
// Like std::is_invocable_r from C++17, but works only for objects with call
|
||||||
// operators. See the note on call_result_t.
|
// operators. See the note on call_result_t.
|
||||||
|
@ -192,13 +192,15 @@ TEST(TypeTraits, IsInvocableRV) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The first overload is callable for const and non-const rvalues and lvalues.
|
// The first overload is callable for const and non-const rvalues and lvalues.
|
||||||
// It can be used to obtain an int, void, or anything int is convertible too.
|
// It can be used to obtain an int, cv void, or anything int is convertible
|
||||||
|
// to.
|
||||||
static_assert(internal::is_callable_r<int, C>::value, "");
|
static_assert(internal::is_callable_r<int, C>::value, "");
|
||||||
static_assert(internal::is_callable_r<int, C&>::value, "");
|
static_assert(internal::is_callable_r<int, C&>::value, "");
|
||||||
static_assert(internal::is_callable_r<int, const C>::value, "");
|
static_assert(internal::is_callable_r<int, const C>::value, "");
|
||||||
static_assert(internal::is_callable_r<int, const C&>::value, "");
|
static_assert(internal::is_callable_r<int, const C&>::value, "");
|
||||||
|
|
||||||
static_assert(internal::is_callable_r<void, C>::value, "");
|
static_assert(internal::is_callable_r<void, C>::value, "");
|
||||||
|
static_assert(internal::is_callable_r<const volatile void, C>::value, "");
|
||||||
static_assert(internal::is_callable_r<char, C>::value, "");
|
static_assert(internal::is_callable_r<char, C>::value, "");
|
||||||
|
|
||||||
// It's possible to provide an int. If it's given to an lvalue, the result is
|
// It's possible to provide an int. If it's given to an lvalue, the result is
|
||||||
@ -217,6 +219,32 @@ TEST(TypeTraits, IsInvocableRV) {
|
|||||||
static_assert(!internal::is_callable_r<void, C, std::string>::value, "");
|
static_assert(!internal::is_callable_r<void, C, std::string>::value, "");
|
||||||
static_assert(!internal::is_callable_r<void, C, int, int>::value, "");
|
static_assert(!internal::is_callable_r<void, C, int, int>::value, "");
|
||||||
|
|
||||||
|
// In C++17 and above, where it's guaranteed that functions can return
|
||||||
|
// non-moveable objects, everything should work fine for non-moveable rsult
|
||||||
|
// types too.
|
||||||
|
#if defined(__cplusplus) && __cplusplus >= 201703L
|
||||||
|
{
|
||||||
|
struct NonMoveable {
|
||||||
|
NonMoveable() = default;
|
||||||
|
NonMoveable(NonMoveable&&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(!std::is_move_constructible_v<NonMoveable>);
|
||||||
|
|
||||||
|
struct Callable {
|
||||||
|
NonMoveable operator()() { return NonMoveable(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(internal::is_callable_r<NonMoveable, Callable>::value);
|
||||||
|
static_assert(internal::is_callable_r<void, Callable>::value);
|
||||||
|
static_assert(
|
||||||
|
internal::is_callable_r<const volatile void, Callable>::value);
|
||||||
|
|
||||||
|
static_assert(!internal::is_callable_r<int, Callable>::value);
|
||||||
|
static_assert(!internal::is_callable_r<NonMoveable, Callable, int>::value);
|
||||||
|
}
|
||||||
|
#endif // C++17 and above
|
||||||
|
|
||||||
// Nothing should choke when we try to call other arguments besides directly
|
// Nothing should choke when we try to call other arguments besides directly
|
||||||
// callable objects, but they should not show up as callable.
|
// callable objects, but they should not show up as callable.
|
||||||
static_assert(!internal::is_callable_r<void, int>::value, "");
|
static_assert(!internal::is_callable_r<void, int>::value, "");
|
||||||
|
Loading…
Reference in New Issue
Block a user