Skip to content

Commit

Permalink
Fix #81 - Add support for std::invoke-like calls in apply.
Browse files Browse the repository at this point in the history
Also adds noexcept spec.
  • Loading branch information
jehelset authored May 30, 2024
1 parent c590834 commit c6c63af
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 10 deletions.
14 changes: 13 additions & 1 deletion include/kumi/detail/concepts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,26 @@ namespace kumi::_

template<typename F, size_t... Is, typename Tuple>
struct supports_apply_t<F, std::index_sequence<Is...>, Tuple>
: std::is_invocable<F, member_t<Is,Tuple>...>
: std::is_invocable<F, decltype(get<Is>(std::declval<Tuple &&>()))...>
{
};

template<typename F, typename Tuple>
concept supports_apply = _::
supports_apply_t<F, std::make_index_sequence<size<Tuple>::value>, Tuple>::value;

template<typename F, typename Indices, typename Tuple> struct supports_nothrow_apply_t;

template<typename F, size_t... Is, typename Tuple>
struct supports_nothrow_apply_t<F, std::index_sequence<Is...>, Tuple>
: std::is_nothrow_invocable<F, decltype(get<Is>(std::declval<Tuple &&>()))...>
{
};

template<typename F, typename Tuple>
concept supports_nothrow_apply = _::
supports_nothrow_apply_t<F, std::make_index_sequence<size<Tuple>::value>, Tuple>::value;

template<typename F, typename... Tuples>
concept supports_call = _::
supports_call_t<F, std::make_index_sequence<(size<Tuples>::value, ...)>, Tuples...>::value;
Expand Down
48 changes: 44 additions & 4 deletions include/kumi/utils/apply.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,32 @@

namespace kumi
{

namespace _{
template<typename T>
inline constexpr bool is_reference_wrapper_v =
!std::is_same_v<std::decay_t<typename std::unwrap_reference<T &&>::type>,
typename std::unwrap_ref_decay<T &&>::type>;

template<typename T>
struct apply_object_unwrap{
using type = T &&;
};
template<typename T>
requires is_reference_wrapper_v<T>
struct apply_object_unwrap<T>{
using type = typename std::remove_cvref_t<T>::type &;
};
template<typename T>
requires std::is_pointer_v<std::remove_cvref_t<T>>
struct apply_object_unwrap<T>{
using type = std::remove_pointer_t<std::remove_cvref_t<T>> &;
};
template<typename T>
using apply_object_unwrap_t = typename apply_object_unwrap<T>::type;

}

//================================================================================================
//! @ingroup transforms
//! @brief Invoke the Callable object f with a tuple of arguments.
Expand All @@ -34,18 +60,32 @@ namespace kumi
//! @include doc/apply.cpp
//================================================================================================
template<typename Function, product_type Tuple>
constexpr decltype(auto) apply(Function &&f, Tuple &&t)
constexpr decltype(auto) apply(Function &&f, Tuple &&t) noexcept(_::supports_nothrow_apply<Function &&, Tuple &&>)
requires _::supports_apply<Function&&, Tuple&&>
{
if constexpr(sized_product_type<Tuple,0>) return KUMI_FWD(f)();
if constexpr(sized_product_type<Tuple,0>) return KUMI_FWD(f)();
else if constexpr (std::is_member_pointer_v<std::decay_t<Function>>)
return [&]<std::size_t... I>(std::index_sequence<I...>) -> decltype(auto){
auto &&w = [](auto &&y) -> decltype(auto){
if constexpr(_::is_reference_wrapper_v<decltype(y)>)
return y.get();
else if constexpr(std::is_pointer_v<std::remove_cvref_t<decltype(y)>>)
return *y;
else
return KUMI_FWD(y);
}(get<0>(KUMI_FWD(t)));
if constexpr(std::is_member_object_pointer_v<std::remove_cvref_t<decltype(f)>>)
return KUMI_FWD(w).*f;
else
return (KUMI_FWD(w).*f)(get<I + 1>(KUMI_FWD(t))...);
}
(std::make_index_sequence<size<Tuple>::value - 1>());
else
{
return [&]<std::size_t... I>(std::index_sequence<I...>) -> decltype(auto)
{
return KUMI_FWD(f)(get<I>(KUMI_FWD(t))...);
}
(std::make_index_sequence<size<Tuple>::value>());
}
}

namespace result
Expand Down
55 changes: 50 additions & 5 deletions standalone/kumi/tuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,21 @@ namespace kumi::_
template<typename F, typename Indices, typename Tuple> struct supports_apply_t;
template<typename F, size_t... Is, typename Tuple>
struct supports_apply_t<F, std::index_sequence<Is...>, Tuple>
: std::is_invocable<F, member_t<Is,Tuple>...>
: std::is_invocable<F, decltype(get<Is>(std::declval<Tuple &&>()))...>
{
};
template<typename F, typename Tuple>
concept supports_apply = _::
supports_apply_t<F, std::make_index_sequence<size<Tuple>::value>, Tuple>::value;
template<typename F, typename Indices, typename Tuple> struct supports_nothrow_apply_t;
template<typename F, size_t... Is, typename Tuple>
struct supports_nothrow_apply_t<F, std::index_sequence<Is...>, Tuple>
: std::is_nothrow_invocable<F, decltype(get<Is>(std::declval<Tuple &&>()))...>
{
};
template<typename F, typename Tuple>
concept supports_nothrow_apply = _::
supports_nothrow_apply_t<F, std::make_index_sequence<size<Tuple>::value>, Tuple>::value;
template<typename F, typename... Tuples>
concept supports_call = _::
supports_call_t<F, std::make_index_sequence<(size<Tuples>::value, ...)>, Tuples...>::value;
Expand Down Expand Up @@ -516,19 +525,55 @@ namespace kumi
}
namespace kumi
{
namespace _{
template<typename T>
inline constexpr bool is_reference_wrapper_v =
!std::is_same_v<std::decay_t<typename std::unwrap_reference<T &&>::type>,
typename std::unwrap_ref_decay<T &&>::type>;
template<typename T>
struct apply_object_unwrap{
using type = T &&;
};
template<typename T>
requires is_reference_wrapper_v<T>
struct apply_object_unwrap<T>{
using type = typename std::remove_cvref_t<T>::type &;
};
template<typename T>
requires std::is_pointer_v<std::remove_cvref_t<T>>
struct apply_object_unwrap<T>{
using type = std::remove_pointer_t<std::remove_cvref_t<T>> &;
};
template<typename T>
using apply_object_unwrap_t = typename apply_object_unwrap<T>::type;
}
template<typename Function, product_type Tuple>
constexpr decltype(auto) apply(Function &&f, Tuple &&t)
constexpr decltype(auto) apply(Function &&f, Tuple &&t) noexcept(_::supports_nothrow_apply<Function &&, Tuple &&>)
requires _::supports_apply<Function&&, Tuple&&>
{
if constexpr(sized_product_type<Tuple,0>) return KUMI_FWD(f)();
if constexpr(sized_product_type<Tuple,0>) return KUMI_FWD(f)();
else if constexpr (std::is_member_pointer_v<std::decay_t<Function>>)
return [&]<std::size_t... I>(std::index_sequence<I...>) -> decltype(auto){
auto &&w = [](auto &&y) -> decltype(auto){
if constexpr(_::is_reference_wrapper_v<decltype(y)>)
return y.get();
else if constexpr(std::is_pointer_v<std::remove_cvref_t<decltype(y)>>)
return *y;
else
return KUMI_FWD(y);
}(get<0>(KUMI_FWD(t)));
if constexpr(std::is_member_object_pointer_v<std::remove_cvref_t<decltype(f)>>)
return KUMI_FWD(w).*f;
else
return (KUMI_FWD(w).*f)(get<I + 1>(KUMI_FWD(t))...);
}
(std::make_index_sequence<size<Tuple>::value - 1>());
else
{
return [&]<std::size_t... I>(std::index_sequence<I...>) -> decltype(auto)
{
return KUMI_FWD(f)(get<I>(KUMI_FWD(t))...);
}
(std::make_index_sequence<size<Tuple>::value>());
}
}
namespace result
{
Expand Down
40 changes: 40 additions & 0 deletions test/unit/apply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::reference_wrapper<C>, int> u1{c,1};
kumi::tuple<C *, int> u2{&c,2};
auto u3 = kumi::make_tuple(std::ref(c));
kumi::tuple<std::reference_wrapper<C>> 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<F,Tuple> behavior")
Expand Down

0 comments on commit c6c63af

Please sign in to comment.