From 1f1e50b306a67df8ed6a7b4e1144674ca0fa1f9a Mon Sep 17 00:00:00 2001 From: Lukas Bergdoll Date: Sun, 28 Apr 2019 20:40:51 +0200 Subject: [PATCH] Make inplace_function's constructor SFINAE on non-callables. Before this patch, `is_convertible_v>` was `true`. After this patch, it's `false`. For modern guidance on the SFINAE requirements of function wrapper constructors, I think the most up-to-date guideline is the `function_ref` paper: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0792r3.html#specification Notice that `inplace_function` does not (yet) respect cv-qualified abominable types as in `inplace_function`, so, that wrinkle of the `function_ref` paper doesn't apply to us. Thanks to @Voultapher for the patch! Fixes #149. --- SG14/inplace_function.h | 43 ++++++++++-- SG14_test/inplace_function_test.cpp | 104 ++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 6 deletions(-) diff --git a/SG14/inplace_function.h b/SG14/inplace_function.h index cddd81ee..0ccf26b0 100644 --- a/SG14/inplace_function.h +++ b/SG14/inplace_function.h @@ -150,6 +150,39 @@ struct is_valid_inplace_dst : std::true_type ); }; +// C++11 MSVC compatible implementation of std::is_invocable_r. + +template void accept(R); + +template struct is_invocable_r_impl : std::false_type {}; + +template struct is_invocable_r_impl< + decltype(std::declval()(std::declval()...), void()), + void, + F, + Args... +> : std::true_type {}; + +template struct is_invocable_r_impl< + decltype(std::declval()(std::declval()...), void()), + const void, + F, + Args... +> : std::true_type {}; + +template struct is_invocable_r_impl< + decltype(accept(std::declval()(std::declval()...))), + R, + F, + Args... +> : std::true_type {}; + +template using is_invocable_r = is_invocable_r_impl< + void, + R, + F, + Args... +>; } // namespace inplace_function_detail template< @@ -190,15 +223,13 @@ class inplace_function template< typename T, typename C = std::decay_t, - typename = std::enable_if_t::value> + typename = std::enable_if_t< + !inplace_function_detail::is_inplace_function::value + && inplace_function_detail::is_invocable_r::value + > > inplace_function(T&& closure) { -#if __cplusplus >= 201703L - static_assert(std::is_invocable_r::value, - "inplace_function cannot be constructed from non-callable type" - ); -#endif static_assert(std::is_copy_constructible::value, "inplace_function cannot be constructed from non-copyable type" ); diff --git a/SG14_test/inplace_function_test.cpp b/SG14_test/inplace_function_test.cpp index a55bee35..38712fb2 100644 --- a/SG14_test/inplace_function_test.cpp +++ b/SG14_test/inplace_function_test.cpp @@ -489,6 +489,108 @@ static void test_return_by_move() assert(InstrumentedCopyConstructor::moves == 1); } +namespace +{ + struct NoDefaultCtor + { + int val; + explicit NoDefaultCtor(int v) : val{v} {} + }; +} // anonymous namespace + +static int overloaded_function(stdext::inplace_function&& exec) +{ + return exec(); +} + +static int overloaded_function(stdext::inplace_function&& exec) +{ + return exec(42); +} + +static void test_is_invocable() +{ + using C_Int1 = int(); + using C_Int2 = int(int); + using C_Void = void(int&); + + using stdext::inplace_function_detail::is_invocable_r; + + static_assert(is_invocable_r::value, ""); + static_assert(! is_invocable_r::value, ""); + static_assert(! is_invocable_r::value, ""); + + static_assert(is_invocable_r::value, ""); + static_assert(! is_invocable_r::value, ""); + static_assert(! is_invocable_r::value, ""); + + static_assert(is_invocable_r::value, ""); + static_assert(! is_invocable_r::value, ""); + + // Testing widening and narrowing conversions, and the "conversion" to void. + static_assert(is_invocable_r::value, ""); + static_assert(is_invocable_r::value, ""); + static_assert(is_invocable_r::value, ""); + + // Testing the conversion from void to int, which should definitely not be allowed. + static_assert(! is_invocable_r::value, ""); + + // cppreference: + // > Determines whether Fn can be invoked with the arguments ArgTypes... + // > to yield a result that is convertible to R. + // + // void is treated specially because a functions return value can be ignored. + static_assert(is_invocable_r::value, ""); + static_assert(is_invocable_r::value, ""); + + // Regression tests for both is_invocable and is_convertible. + static_assert(is_invocable_r::value, ""); + static_assert(is_invocable_r::value, ""); +} + +static void test_overloading() +{ + EXPECT_EQ(overloaded_function([]() -> int { return 3; }), 3); + EXPECT_EQ(overloaded_function([](int arg) -> int { return arg; }), 42); + + using std::is_convertible; + using stdext::inplace_function; + + const auto a = []() -> int { return 3; }; + static_assert(is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + + const auto b = [](int&) -> void {}; + static_assert(is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + + const auto c = [](int, NoDefaultCtor) -> int { return 3; }; + static_assert(is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + + const auto d = []() -> void {}; + static_assert(is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + + static_assert(is_convertible>::value, ""); + static_assert(is_convertible>::value, ""); + + // Same as a, but not const. + auto e = []() -> int { return 3; }; + static_assert(is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + + // Same as a, but not const and mutable. + auto f = []() mutable -> int { return 3; }; + static_assert(is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); + static_assert(!is_convertible>::value, ""); +} void sg14_test::inplace_function_test() { @@ -576,6 +678,8 @@ void sg14_test::inplace_function_test() test_move_construction_from_smaller_buffer_is_noexcept(); test_is_convertible(); test_return_by_move(); + test_is_invocable(); + test_overloading(); } #ifdef TEST_MAIN