From c6c63af62d67cc868328c7312c23e42ab8b30412 Mon Sep 17 00:00:00 2001 From: jehelset <47066444+jehelset@users.noreply.github.com> Date: Thu, 30 May 2024 20:24:24 +0200 Subject: [PATCH] Fix #81 - Add support for `std::invoke`-like calls in `apply`. Also adds noexcept spec. --- include/kumi/detail/concepts.hpp | 14 +++++++- include/kumi/utils/apply.hpp | 48 +++++++++++++++++++++++++--- standalone/kumi/tuple.hpp | 55 +++++++++++++++++++++++++++++--- test/unit/apply.cpp | 40 +++++++++++++++++++++++ 4 files changed, 147 insertions(+), 10 deletions(-) diff --git a/include/kumi/detail/concepts.hpp b/include/kumi/detail/concepts.hpp index 66f94f9..b315bfa 100644 --- a/include/kumi/detail/concepts.hpp +++ b/include/kumi/detail/concepts.hpp @@ -87,7 +87,7 @@ namespace kumi::_ template struct supports_apply_t, Tuple> - : std::is_invocable...> + : std::is_invocable(std::declval()))...> { }; @@ -95,6 +95,18 @@ namespace kumi::_ concept supports_apply = _:: supports_apply_t::value>, Tuple>::value; + template struct supports_nothrow_apply_t; + + template + struct supports_nothrow_apply_t, Tuple> + : std::is_nothrow_invocable(std::declval()))...> + { + }; + + template + concept supports_nothrow_apply = _:: + supports_nothrow_apply_t::value>, Tuple>::value; + template concept supports_call = _:: supports_call_t::value, ...)>, Tuples...>::value; diff --git a/include/kumi/utils/apply.hpp b/include/kumi/utils/apply.hpp index 2569214..8c90830 100644 --- a/include/kumi/utils/apply.hpp +++ b/include/kumi/utils/apply.hpp @@ -9,6 +9,32 @@ namespace kumi { + + namespace _{ + template + inline constexpr bool is_reference_wrapper_v = + !std::is_same_v::type>, + typename std::unwrap_ref_decay::type>; + + template + struct apply_object_unwrap{ + using type = T &&; + }; + template + requires is_reference_wrapper_v + struct apply_object_unwrap{ + using type = typename std::remove_cvref_t::type &; + }; + template + requires std::is_pointer_v> + struct apply_object_unwrap{ + using type = std::remove_pointer_t> &; + }; + template + using apply_object_unwrap_t = typename apply_object_unwrap::type; + + } + //================================================================================================ //! @ingroup transforms //! @brief Invoke the Callable object f with a tuple of arguments. @@ -34,18 +60,32 @@ namespace kumi //! @include doc/apply.cpp //================================================================================================ template - constexpr decltype(auto) apply(Function &&f, Tuple &&t) + constexpr decltype(auto) apply(Function &&f, Tuple &&t) noexcept(_::supports_nothrow_apply) requires _::supports_apply { - if constexpr(sized_product_type) return KUMI_FWD(f)(); + if constexpr(sized_product_type) return KUMI_FWD(f)(); + else if constexpr (std::is_member_pointer_v>) + return [&](std::index_sequence) -> decltype(auto){ + auto &&w = [](auto &&y) -> decltype(auto){ + if constexpr(_::is_reference_wrapper_v) + return y.get(); + else if constexpr(std::is_pointer_v>) + return *y; + else + return KUMI_FWD(y); + }(get<0>(KUMI_FWD(t))); + if constexpr(std::is_member_object_pointer_v>) + return KUMI_FWD(w).*f; + else + return (KUMI_FWD(w).*f)(get(KUMI_FWD(t))...); + } + (std::make_index_sequence::value - 1>()); else - { return [&](std::index_sequence) -> decltype(auto) { return KUMI_FWD(f)(get(KUMI_FWD(t))...); } (std::make_index_sequence::value>()); - } } namespace result diff --git a/standalone/kumi/tuple.hpp b/standalone/kumi/tuple.hpp index f5cdb7b..4564dfe 100644 --- a/standalone/kumi/tuple.hpp +++ b/standalone/kumi/tuple.hpp @@ -111,12 +111,21 @@ namespace kumi::_ template struct supports_apply_t; template struct supports_apply_t, Tuple> - : std::is_invocable...> + : std::is_invocable(std::declval()))...> { }; template concept supports_apply = _:: supports_apply_t::value>, Tuple>::value; + template struct supports_nothrow_apply_t; + template + struct supports_nothrow_apply_t, Tuple> + : std::is_nothrow_invocable(std::declval()))...> + { + }; + template + concept supports_nothrow_apply = _:: + supports_nothrow_apply_t::value>, Tuple>::value; template concept supports_call = _:: supports_call_t::value, ...)>, Tuples...>::value; @@ -516,19 +525,55 @@ namespace kumi } namespace kumi { + namespace _{ + template + inline constexpr bool is_reference_wrapper_v = + !std::is_same_v::type>, + typename std::unwrap_ref_decay::type>; + template + struct apply_object_unwrap{ + using type = T &&; + }; + template + requires is_reference_wrapper_v + struct apply_object_unwrap{ + using type = typename std::remove_cvref_t::type &; + }; + template + requires std::is_pointer_v> + struct apply_object_unwrap{ + using type = std::remove_pointer_t> &; + }; + template + using apply_object_unwrap_t = typename apply_object_unwrap::type; + } template - constexpr decltype(auto) apply(Function &&f, Tuple &&t) + constexpr decltype(auto) apply(Function &&f, Tuple &&t) noexcept(_::supports_nothrow_apply) requires _::supports_apply { - if constexpr(sized_product_type) return KUMI_FWD(f)(); + if constexpr(sized_product_type) return KUMI_FWD(f)(); + else if constexpr (std::is_member_pointer_v>) + return [&](std::index_sequence) -> decltype(auto){ + auto &&w = [](auto &&y) -> decltype(auto){ + if constexpr(_::is_reference_wrapper_v) + return y.get(); + else if constexpr(std::is_pointer_v>) + return *y; + else + return KUMI_FWD(y); + }(get<0>(KUMI_FWD(t))); + if constexpr(std::is_member_object_pointer_v>) + return KUMI_FWD(w).*f; + else + return (KUMI_FWD(w).*f)(get(KUMI_FWD(t))...); + } + (std::make_index_sequence::value - 1>()); else - { return [&](std::index_sequence) -> decltype(auto) { return KUMI_FWD(f)(get(KUMI_FWD(t))...); } (std::make_index_sequence::value>()); - } } namespace result { diff --git a/test/unit/apply.cpp b/test/unit/apply.cpp index 62f3324..e45bd0f 100644 --- a/test/unit/apply.cpp +++ b/test/unit/apply.cpp @@ -12,15 +12,55 @@ struct A { void operator()(auto&&...) & {} }; struct B { void operator()(auto&&) & {} }; +struct C { void f(int){} int x; }; TTS_CASE("Check apply SFINAE compliance") { A a; B b; + C c; auto t = kumi::make_tuple(1,2,3,4); + auto u0 = kumi::make_tuple(std::ref(c), 1); + kumi::tuple, int> u1{c,1}; + kumi::tuple u2{&c,2}; + auto u3 = kumi::make_tuple(std::ref(c)); + kumi::tuple> u4{c}; + auto u5 = kumi::make_tuple(&c); TTS_EXPECT_COMPILES(a, t, { kumi::apply(a, t); } ); TTS_EXPECT_NOT_COMPILES(b, t, { kumi::apply(b, t); } ); + TTS_EXPECT_COMPILES(u0, { kumi::apply(&C::f, u0); } ); + TTS_EXPECT_COMPILES(u1, { kumi::apply(&C::f, u1); } ); + TTS_EXPECT_COMPILES(u2, { kumi::apply(&C::f, u2); } ); + TTS_EXPECT_COMPILES(u3, { kumi::apply(&C::x, u3); } ); + TTS_EXPECT_COMPILES(u4, { kumi::apply(&C::x, u4); } ); + TTS_EXPECT_COMPILES(u5, { kumi::apply(&C::x, u5); } ); + + struct F0{ + void operator()() && {} + }; + F0 f0{}; + TTS_EXPECT_COMPILES(f0, { kumi::apply(std::move(f0), kumi::tuple{}); }); + TTS_EXPECT_NOT_COMPILES(f0, { kumi::apply(f0, kumi::tuple{}); }); + TTS_EXPECT_NOT_COMPILES(f0, { kumi::apply(std::ref(f0), kumi::tuple{}); }); + struct F1{ + void operator()() & {} + }; + F1 f1{}; + TTS_EXPECT_NOT_COMPILES(f1, { kumi::apply(std::move(f1), kumi::tuple{}); }); + TTS_EXPECT_COMPILES(f1, { kumi::apply(f1, kumi::tuple{}); }); + TTS_EXPECT_COMPILES(f1, { kumi::apply(std::ref(f1), kumi::tuple{}); }); + + struct F2{ + void operator()(int &&){} + }; + F2 f2{}; + TTS_EXPECT_COMPILES(f2, { kumi::apply(f2, kumi::make_tuple(2)); }); + int i2 = 2; + TTS_EXPECT_NOT_COMPILES(f2, i2, { kumi::apply(f2, kumi::forward_as_tuple(i2)); }); + TTS_EXPECT_COMPILES(f2, i2, { kumi::apply(f2, kumi::forward_as_tuple(std::move(i2))); }); + auto t2 = kumi::forward_as_tuple(std::move(i2)); + TTS_EXPECT_NOT_COMPILES(f2, t2, { kumi::apply(f2, t2); }); }; TTS_CASE("Check result::apply behavior")