From dcf0d7ebdfb6607e98ee44f52655a23b12476acc Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 23 May 2020 19:16:07 +0300 Subject: [PATCH] Squash-merge PR #50 --- doc/mp11/changelog.adoc | 1 + doc/mp11/tuple.adoc | 12 +++ include/boost/mp11/tuple.hpp | 77 ++++++++++++++ test/Jamfile | 2 + test/tuple_transform.cpp | 190 +++++++++++++++++++++++++++++++++++ test/tuple_transform_cx.cpp | 75 ++++++++++++++ 6 files changed, 357 insertions(+) create mode 100644 test/tuple_transform.cpp create mode 100644 test/tuple_transform_cx.cpp diff --git a/doc/mp11/changelog.adoc b/doc/mp11/changelog.adoc index 7d707f8..94822fa 100644 --- a/doc/mp11/changelog.adoc +++ b/doc/mp11/changelog.adoc @@ -13,6 +13,7 @@ http://www.boost.org/LICENSE_1_0.txt ## Changes in 1.74.0 * Improve compilation performance of `mp_with_index` for large `N` +* Added `tuple_transform` (contributed by Hans Dembinski) ## Changes in 1.73.0 diff --git a/doc/mp11/tuple.adoc b/doc/mp11/tuple.adoc index 46baab3..25da1fd 100644 --- a/doc/mp11/tuple.adoc +++ b/doc/mp11/tuple.adoc @@ -36,3 +36,15 @@ The name of the function doesn't match the {cpp}17 one to avoid ambiguities when expression `f(std::get(std::forward(tp)))` for `J` in 0..`N-1`, where `N` is `std::tuple_size::type>::value`. Returns `std::forward(f)`. + +## tuple_transform(f, tp) + + template constexpr /*...*/ tuple_transform(F const& f, Tps const&... tps); + +`tuple_transform(f, tps...)` accepts a function object `f` followed by one or more tuples of equal length +(`std::pair` is also accepted). The callable `f` must accept as many arguments as there are tuples. The +function object is called with the first elements of each tuple, then with the second element of each tuple, +and so on, by evaluating the expression `f(std::get(tps)...)` for `J` in 0..`N-1`, where `N` is +`std::tuple_size::value` for the first tuple `Tp`. The results are returned as a `Tp` with `Ts...` +deduced from the return values of `f`. Warning: The order in which the elements of the tuples are processed +is undefined. Calling `f` should not have side-effects. diff --git a/include/boost/mp11/tuple.hpp b/include/boost/mp11/tuple.hpp index c65be1b..872f70f 100644 --- a/include/boost/mp11/tuple.hpp +++ b/include/boost/mp11/tuple.hpp @@ -86,6 +86,83 @@ template BOOST_MP11_CONSTEXPR F tuple_for_each( Tp && tp, F & return detail::tuple_for_each_impl( std::forward(tp), seq(), std::forward(f) ); } +// tuple_transform +namespace detail +{ + +template +struct tuple_transform_caller { + F const& f_; + std::tuple const tps_; + + BOOST_MP11_CONSTEXPR tuple_transform_caller( Tp const& tp, F const& f, Tps const&... tps ) + : f_{f}, tps_{tp, tps...} {} + + template + BOOST_MP11_CONSTEXPR auto operator()( + std::integral_constant, + integer_sequence) const + -> decltype(f_(std::get(std::get(tps_))...)) + { + return f_(std::get(std::get(tps_))...); + } +}; + +template +struct tuple_transform_caller { + F const& f_; + Tp const& tp_; + + BOOST_MP11_CONSTEXPR tuple_transform_caller( Tp const& tp, F const& f ) + : f_{f}, tp_{tp} {} + + template + BOOST_MP11_CONSTEXPR auto operator()( + std::integral_constant, + integer_sequence + ) const + -> decltype(f_(std::get(tp_))) + { + return f_(std::get(tp_)); + } +}; + +template< + template class Tp, + class... Ts, + class... Us, + std::size_t... J, + class Seq = make_index_sequence, // tuple sequence + class R = Tp, Us...>>()( + std::integral_constant{}, Seq{} + ))...> +> +BOOST_MP11_CONSTEXPR R tuple_transform_impl( + tuple_transform_caller, Us...> c, + integer_sequence) +{ + return R( c(std::integral_constant{}, Seq{})... ); +} + +} // namespace detail + +// warning: evaluation order is undefined +template< + class F, + class Tp, + class... Tps, + class Seq = make_index_sequence::value> // element sequence +> +BOOST_MP11_CONSTEXPR auto tuple_transform( F const& f, Tp const& tp, Tps const&... tps ) + -> decltype(detail::tuple_transform_impl( + std::declval>(), Seq{} + )) +{ + return detail::tuple_transform_impl( + detail::tuple_transform_caller{ tp, f, tps... }, Seq{} + ); +} + } // namespace mp11 } // namespace boost diff --git a/test/Jamfile b/test/Jamfile index dc2d062..15fef4c 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -156,6 +156,8 @@ run tuple_apply.cpp ; compile tuple_apply_cx.cpp ; run construct_from_tuple.cpp ; compile construct_from_tuple_cx.cpp ; +run tuple_transform.cpp ; +compile tuple_transform_cx.cpp ; # set run mp_set_contains.cpp ; diff --git a/test/tuple_transform.cpp b/test/tuple_transform.cpp new file mode 100644 index 0000000..d7d0b79 --- /dev/null +++ b/test/tuple_transform.cpp @@ -0,0 +1,190 @@ + +// Copyright 2020 Hans Dembinski. +// +// Distributed under the Boost Software License, Version 1.0. +// +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include +#include +#include +#include + +// family of test types with state +template +struct T { + int value; + T() : value{N} {}; + explicit T(int n) : value{n} {} +}; + +template +std::ostream& operator<<( std::ostream& os, T const& t ) +{ + os << t.value; + return os; +} + +// test function changes type and value +struct F { + template T operator()( T a, T b={} ) const + { + return T{a.value + b.value + 1}; + } +}; + +int main() +{ + using boost::mp11::tuple_transform; + + { + std::tuple, T<2>, T<3>> tp; + std::tuple, T<5>, T<6>> tp2; + + { + std::tuple, T<3>, T<4>> s = tuple_transform( F{}, tp ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + BOOST_TEST_EQ( std::get<2>(s).value, 5 ); + } + + { + std::tuple, T<3>, T<4>> s = tuple_transform( F{}, std::move(tp) ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + BOOST_TEST_EQ( std::get<2>(s).value, 5 ); + } + + { + std::tuple, T<7>, T<9>> s = tuple_transform( F{}, tp, tp2 ); + BOOST_TEST_EQ( std::get<0>(s).value, 6 ); + BOOST_TEST_EQ( std::get<1>(s).value, 8 ); + BOOST_TEST_EQ( std::get<2>(s).value, 10 ); + } + + { + std::tuple, T<7>, T<9>> s = tuple_transform( + F{}, std::move(tp), std::move(tp2) + ); + BOOST_TEST_EQ( std::get<0>(s).value, 6 ); + BOOST_TEST_EQ( std::get<1>(s).value, 8 ); + BOOST_TEST_EQ( std::get<2>(s).value, 10 ); + } + } + + { + std::tuple, T<2>, T<3>> const tp; + std::tuple, T<5>, T<6>> const tp2; + + { + std::tuple, T<3>, T<4>> s = tuple_transform( F{}, tp ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + BOOST_TEST_EQ( std::get<2>(s).value, 5 ); + } + + { + std::tuple, T<3>, T<4>> s = tuple_transform( F{}, std::move(tp) ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + BOOST_TEST_EQ( std::get<2>(s).value, 5 ); + } + + { + std::tuple, T<7>, T<9>> s = tuple_transform( F{}, tp, tp2 ); + BOOST_TEST_EQ( std::get<0>(s).value, 6 ); + BOOST_TEST_EQ( std::get<1>(s).value, 8 ); + BOOST_TEST_EQ( std::get<2>(s).value, 10 ); + } + + { + std::tuple, T<7>, T<9>> s = tuple_transform( + F{}, std::move(tp), std::move(tp2) + ); + BOOST_TEST_EQ( std::get<0>(s).value, 6 ); + BOOST_TEST_EQ( std::get<1>(s).value, 8 ); + BOOST_TEST_EQ( std::get<2>(s).value, 10 ); + } + } + + { + std::pair, T<2>> tp; + std::pair, T<4>> tp2; + + { + std::pair, T<3>> s = tuple_transform( F{}, tp ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + } + + { + std::pair, T<3>> s = tuple_transform( F{}, std::move(tp) ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + } + + { + std::pair, T<6>> s = tuple_transform( F{}, tp, tp2 ); + BOOST_TEST_EQ( std::get<0>(s).value, 5 ); + BOOST_TEST_EQ( std::get<1>(s).value, 7 ); + } + + { + std::pair, T<6>> s = tuple_transform( + F{}, std::move(tp), std::move(tp2) + ); + BOOST_TEST_EQ( std::get<0>(s).value, 5 ); + BOOST_TEST_EQ( std::get<1>(s).value, 7 ); + } + } + + { + std::pair, T<2>> const tp; + std::pair, T<4>> const tp2; + + { + std::pair, T<3>> s = tuple_transform( F{}, tp ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + } + + { + std::pair, T<3>> s = tuple_transform( F{}, std::move(tp) ); + BOOST_TEST_EQ( std::get<0>(s).value, 3 ); + BOOST_TEST_EQ( std::get<1>(s).value, 4 ); + } + + { + std::pair, T<6>> s = tuple_transform( F{}, tp, tp2 ); + BOOST_TEST_EQ( std::get<0>(s).value, 5 ); + BOOST_TEST_EQ( std::get<1>(s).value, 7 ); + } + + { + std::pair, T<6>> s = tuple_transform( + F{}, std::move(tp), std::move(tp2) + ); + BOOST_TEST_EQ( std::get<0>(s).value, 5 ); + BOOST_TEST_EQ( std::get<1>(s).value, 7 ); + } + } + + { + std::tuple<> tp; + + { + auto s = tuple_transform( F{}, tp ); + BOOST_TEST_TRAIT_TRUE((std::is_same>)); + } + + { + auto s = tuple_transform( F{}, std::move(tp) ); + BOOST_TEST_TRAIT_TRUE((std::is_same>)); + } + } + + return boost::report_errors(); +} diff --git a/test/tuple_transform_cx.cpp b/test/tuple_transform_cx.cpp new file mode 100644 index 0000000..2b07d52 --- /dev/null +++ b/test/tuple_transform_cx.cpp @@ -0,0 +1,75 @@ + +// Copyright 2020 Hans Dembinski. +// +// Distributed under the Boost Software License, Version 1.0. +// +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include +#include + +// Technically std::tuple isn't constexpr enabled in C++11, but it works with libstdc++ + +#if defined( BOOST_MP11_NO_CONSTEXPR ) || ( !defined( __GLIBCXX__ ) && __cplusplus < 201400L ) + +int main() {} + +#else + +#include +#include + +// family of test types with state +template +struct T { + int value; + constexpr T() : value{N} {}; + constexpr explicit T(int n) : value{n} {} +}; + +struct F +{ + template constexpr T operator()( T a, T b={} ) const + { + return T{a.value + b.value + 1}; + } +}; + +int main() +{ + { + constexpr std::tuple, T<2>> tp; + constexpr std::tuple, T<4>> tp2; + + { + constexpr std::tuple, T<3>> r = boost::mp11::tuple_transform( + F{}, tp + ); + static_assert(std::get<0>(r).value == 3, "get<0>(r).value == 3" ); + static_assert(std::get<1>(r).value == 4, "get<1>(r).value == 4" ); + } + + { + constexpr std::tuple, T<6>> r = boost::mp11::tuple_transform( + F{}, tp, tp2 + ); + static_assert(std::get<0>(r).value == 5, "get<0>(r).value == 5" ); + static_assert(std::get<1>(r).value == 7, "get<1>(r).value == 7" ); + } + } + +#if defined( __clang_major__ ) && __clang_major__ == 3 && __clang_minor__ < 9 +// "error: default initialization of an object of const type 'const std::tuple<>' without a user-provided default constructor" +#else + + { + constexpr std::tuple<> tp; + constexpr std::tuple<> r = boost::mp11::tuple_transform( F{}, tp ); + (void)r; + } + +#endif +} + +#endif