diff --git a/source/simple/support/tuple_utils/apply_for.hpp b/source/simple/support/tuple_utils/apply_for.hpp index 504ac61e7db01ab8ff277a0b815b2c09f8255d69..fbf1d9e64c9f23a13429d987b9f6ae6939dc341f 100644 --- a/source/simple/support/tuple_utils/apply_for.hpp +++ b/source/simple/support/tuple_utils/apply_for.hpp @@ -3,15 +3,30 @@ #include <tuple> #include <functional> +#include <cassert> #include "../range.hpp" namespace simple::support { + template <typename T, typename = std::nullptr_t> + struct has_tuple_interface : public std::false_type {}; + + template <typename T> + struct has_tuple_interface<T, decltype + ( + void(std::tuple_size<std::decay_t<T>>::value), + nullptr + ) > : public std::true_type {}; + + template <typename T> + constexpr bool has_tuple_interface_v = has_tuple_interface<T>::value; + // wow this is a mess... you think it'll be optimized? O_o - //TODO: return a variant?? + //TODO: return a variant + //TODO: rename to transform template<typename F, typename First, size_t I = std::tuple_size_v<std::remove_reference_t<First>> - 1, typename... Rest> - [[nodiscard]] constexpr + constexpr decltype(auto) apply_for(size_t index, F&& f, First&& first, Rest&&... rest) { using std::remove_reference_t; @@ -31,9 +46,11 @@ namespace simple::support return apply_for<F, decltype(forward<First_t>(first)), I - 1>(index, forward<F>(f), forward<First_t>(first), forward<remove_reference_t<Rest>>(rest)...); - throw std::logic_error("simple::support::apply_for - this should never happen"); + assert(!"simple::support::apply_for - index out of bounds"); } + //TODO: return a tuple of variants + //TODO: rename to transform template<typename F, typename First, size_t I = std::tuple_size_v<std::remove_reference_t<First>> - 1, typename... Rest> constexpr void apply_for(range<size_t> index_range, F&& f, First&& first, Rest&&... rest) @@ -46,6 +63,126 @@ namespace simple::support } } + constexpr struct tuple_void_t {} tuple_void; + [[nodiscard]] constexpr bool operator==(const tuple_void_t, const tuple_void_t) noexcept { return true; } + [[nodiscard]] constexpr bool operator!=(const tuple_void_t, const tuple_void_t) noexcept { return false; } + + template <typename T> + constexpr decltype(auto) operator,(T&& x, const tuple_void_t) + //TODO: noexcept(-_-) + { + return std::forward<T>(x); + } + + template <typename First, typename... Rest> + constexpr size_t min_tuple_size() + { + size_t result = std::tuple_size_v<First>; + if constexpr(sizeof...(Rest) != 0) + result = std::min(result, min_tuple_size<Rest...>()); + return result; + } + + template <typename... T> + constexpr size_t min_tuple_size_v = min_tuple_size<T...>(); + + template <typename... T> + using min_tuple_index_sequence = std::make_index_sequence<min_tuple_size_v<std::decay_t<T>...>>; + + template <size_t Index, typename Range> + // TODO: enable_if_t<Range is integer_sequence and Index is in Range> + struct iteration_state + { + constexpr static std::integral_constant<decltype(Index), Index> index{}; + constexpr static Range range{}; + }; + template <size_t I, typename R> + [[nodiscard]] constexpr bool + operator==(const iteration_state<I,R>, const iteration_state<I,R>) + noexcept { return true; } + template <size_t I, typename R> + [[nodiscard]] constexpr bool + operator!=(const iteration_state<I,R>, const iteration_state<I,R>) noexcept + { return false; } + + template<typename F> + constexpr decltype(auto) transform(F&& f) + //TODO: noexcept(-_-) + { + return std::apply(std::forward<F>(f)); + } + + template<size_t Index, typename F, typename... Tuples, + std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr, + std::enable_if_t<not std::is_invocable<F, + decltype(get<Index>(std::declval<Tuples>()))...>::value>* = nullptr, + std::enable_if_t<std::is_invocable<F, iteration_state<Index, min_tuple_index_sequence<Tuples...>>, + decltype(get<Index>(std::declval<Tuples>()))...>::value>* = nullptr > + constexpr + decltype(auto) transform(F&& f, Tuples&&... tuples) + //TODO: noexcept(-_-) + { + // std::invoke is not contexpr in C++17 :V + auto info = iteration_state<Index, min_tuple_index_sequence<Tuples...>>{}; + return std::apply(std::forward<F>(f), + std::forward_as_tuple(info, get<Index>(std::forward<Tuples>(tuples))...) ); + } + + template<size_t Index, typename F, typename... Tuples, + std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr, + std::enable_if_t<std::is_invocable<F, + decltype(get<Index>(std::declval<Tuples>()))...>::value>* = nullptr > + constexpr + decltype(auto) transform(F&& f, Tuples&&... tuples) + //TODO: noexcept(-_-) + { + // std::invoke is not contexpr in C++17 :V + return std::apply(std::forward<F>(f), + std::forward_as_tuple(get<Index>(std::forward<Tuples>(tuples))...) ); + } + + template<typename F, typename... Tuples, size_t... Indecies, + std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr> + constexpr + decltype(auto) transform(F&& f, std::index_sequence<Indecies...>, Tuples&&... tuples) + //TODO: noexcept(-_-) + { + //TODO: pass the index sequence through for use in iteration_state + return std::tuple + { + (transform<Indecies>(std::forward<F>(f), std::forward<Tuples>(tuples)...), + tuple_void ) + ... + }; + } + + template<typename F, typename... Tuples, + std::enable_if_t<(has_tuple_interface_v<Tuples> && ...)>* = nullptr> + constexpr + decltype(auto) transform(F&& f, Tuples&&... tuples) + //TODO: noexcept(-_-) + { + return transform(std::forward<F>(f), + min_tuple_index_sequence<Tuples...>{} , + std::forward<Tuples>(tuples)...); + } + + namespace detail + { + template <typename T, typename Tuple, std::size_t... I> + constexpr T from_tuple_impl( Tuple&& t, std::index_sequence<I...> ) + { + return T{get<I>(std::forward<Tuple>(t))...}; + } + } // namespace detail + + template <typename T, typename Tuple> + constexpr T from_tuple( Tuple&& t ) + { + return detail::from_tuple_impl<T>(std::forward<Tuple>(t), + std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>{}); + } + } // namespace simple::support #endif /* end of include guard */ diff --git a/unit_tests/tuple_utils.cpp b/unit_tests/tuple_utils.cpp index ac7e8a23764f0540e9571b2955fe30ae1f9763e9..f1a0a46ab9a8e9827774b4d185d81f3a2b715556 100644 --- a/unit_tests/tuple_utils.cpp +++ b/unit_tests/tuple_utils.cpp @@ -1,9 +1,11 @@ #include <cassert> +#include <string> #include <sstream> -#include <iostream> #include "simple/support/tuple_utils.hpp" +#include "simple/support/function_utils.hpp" using namespace simple::support; +using namespace std::literals; void ApplyFor() { @@ -13,7 +15,7 @@ void ApplyFor() apply_for({0,4}, [&ss](auto&& a) { ss << a; - } ,t); + }, t); assert(ss.str() == "0 one 2 three"); ss.str(""); ss.clear(); @@ -21,7 +23,7 @@ void ApplyFor() apply_for({1,3}, [&ss](auto&& a) { ss << a; - } ,t); + }, t); assert(ss.str() == " one 2"); ss.str(""); ss.clear(); @@ -29,7 +31,7 @@ void ApplyFor() apply_for(3, [&ss](auto&& a) { ss << a; - } ,t); + }, t); assert(ss.str() == " three"); ss.str(""); ss.clear(); @@ -88,12 +90,208 @@ void CarCdr() assert(tref_cdr2 == tref_cdr); assert(tref_cdr2 == std::tuple(12,3)); assert(tref_cdr == std::tuple(12,3)); + + auto tref_cdr3 = tuple_tie_cdr(tref_cdr2); + tuple_car(tref_cdr3) = 99; + assert(std::get<2>(t) == 99); + assert(tref_cdr3 == std::tuple(99)); + + auto tref_cdr4 = tuple_cdr(tref_cdr2); + tuple_car(tref_cdr4) = 88; + assert(std::get<2>(t) != 88); + assert(tref_cdr4 == std::tuple(88)); } } +void TransformBasic() +{ + + assert + (( + transform(std::plus<>{}, + std::tuple{1, "2"s, 3.4}, std::tuple{4, "3"s, 2.1}) + == std::tuple{5, "23"s, 5.5} + )); + + assert + (( + transform(std::plus<>{}, + std::array{1, 2, 3}, std::tuple{3, 2, 1}) + == std::tuple{4, 4, 4} + )); + + assert + (( + transform(std::plus<>(), + std::tuple{1, 2, 3.3}, std::array{3, 2, 1}) + == std::tuple{4, 4, 4.3} + )); + + assert + (( + transform(std::plus<>{}, + std::tuple{1, 2, 3.3, 4, 5}, std::array{3, 2, 1}) + == std::tuple{4, 4, 4.3} + )); + + assert + (( + transform + ( + [](auto... x) { return (x + ...); }, + std::tuple{"1"s, 2}, + std::tuple{"3"s, 4}, + std::tuple{"5"s, 6} + ) + == std::tuple{"135"s, 12} + )); + +} + +struct counter +{ + static size_t inst; + static size_t copy; + static size_t move; + + counter() { ++inst; }; + counter(const counter&) { ++copy; } + counter(counter&&) { ++move; } +}; +size_t counter::inst = 0; +size_t counter::copy = 0; +size_t counter::move = 0; + +void TransformCopyCount() +{ + + transform([](auto&& x) { return std::forward<decltype(x)>(x); }, std::tuple{counter{}, counter{}, counter{}} ); + assert(3 == counter::inst); + assert(0 == counter::copy); + assert(0 < counter::move); + +} + +void TransformReturnVoid() +{ + + assert + (( + transform([](auto...){}, + std::tuple{1, 2, 3.3}, std::array{3, 2, 1}) + == std::tuple{tuple_void, tuple_void, tuple_void} + )); + + assert + (( + transform + ( + overloaded + { + [](std::string, int){}, + std::plus<>{}, + }, + std::tuple{1, "2"s, "3"s}, + std::tuple{3, 2, "1"s} + ) + == std::tuple{4, tuple_void, "31"s} + )); + +} + +template <size_t I, size_t S> +constexpr iteration_state<I,std::make_index_sequence<S>> i_state{}; + +struct +{ + template <size_t Index, typename Range, typename... Values> + auto operator()(iteration_state<Index,Range>, Values... v) + { + return (Range::size() - Index) * (v + ...); + } +} sum_mul_index; + +void TransformIterationState() +{ + + assert + (( + transform([](auto i, auto, auto) { return i; }, + std::tuple{1, 2, 3.3}, std::array{3, 2, 1}) + == std::tuple{i_state<0,3>, i_state<1,3>, i_state<2,3>} + )); + + assert + (( + transform([](auto i, auto, auto) { return i; }, + std::tuple{1, 2, 3.3, 4, 5}, std::array{3, 2, 1}) + == std::tuple{i_state<0,3>, i_state<1,3>, i_state<2,3>} + )); + + assert + (( + transform(sum_mul_index, + std::tuple{1, 2, 3.3}, std::array{3, 2, 1, 0, -1, -2}) + == std::tuple{4*3,4*2,4.3*1} + )); + +} + +struct unlike_tuple {}; +template <typename F> +std::string transform(F&&, const unlike_tuple&) +{return "Don't be too greedy!"; } + +struct like_tuple +{ + int a; + std::string b; + + bool operator==(const like_tuple& other) const + { return a == other.a && b == other.b; } +}; + +template <> +struct std::tuple_size<like_tuple> : public std::integral_constant<size_t, 2> {}; + +template <size_t I, typename T, + std::enable_if_t<std::is_same_v<std::decay_t<T>,like_tuple>>* = nullptr> +decltype(auto) get(T&& t) noexcept +{ + static_assert(I < 2); + if constexpr (I == 0) + { + return std::forward<T>(t).a; + } + else + { + return std::forward<T>(t).b; + } +} + +void TransformNonTuple() +{ + + assert + (( + from_tuple<like_tuple>(transform( + std::plus<>{}, like_tuple{1,"1"s}, std::tuple{2,"2"s} )) + == + like_tuple{3, "12"s} + )); + + assert( transform([](){ return "Too greedy!"; }, unlike_tuple{}) == "Don't be too greedy!" ); + +} + int main() { ApplyFor(); CarCdr(); + TransformBasic(); + TransformCopyCount(); + TransformReturnVoid(); + TransformIterationState(); + TransformNonTuple(); return 0; }