From 1929def002ef7d0b5358139e1e4d70d355b3b8ab Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Tue, 8 Apr 2025 19:40:39 -0600 Subject: [PATCH] :sparkles: Add `for_each_butlast` and `initial_medial_final` Problem: - There is no algorithm that does `for_each` on a range except for the last N elements. - There is a use case for a tripartite operation on a range: one operation for the initial element, another operation for each medial element, and a third operation for the final element. - The return values of `for_each` and `for_each_n` do not follow the law of Useful Return. Solution: - Add `for_each_butlastn`, `for_each_butlast`, and `initial_medial_final`. - Return all useful information from `for_each` and `for_each_n`. --- docs/algorithm.adoc | 55 ++++++++++- include/stdx/algorithm.hpp | 75 ++++++++++++++- include/stdx/tuple.hpp | 7 +- test/algorithm.cpp | 188 ++++++++++++++++++++++++++++++++++++- test/tuple.cpp | 2 +- 5 files changed, 311 insertions(+), 16 deletions(-) diff --git a/docs/algorithm.adoc b/docs/algorithm.adoc index 196855e..71d11b4 100644 --- a/docs/algorithm.adoc +++ b/docs/algorithm.adoc @@ -14,23 +14,74 @@ variadic in its inputs. ---- template constexpr auto for_each(InputIt first, InputIt last, - Operation op, InputItN... first_n) -> Operation; + Operation op, InputItN... first_n) + -> for_each_result; ---- +The return value is equivalent to a `tuple`. +In C\\++20 and later this is a `stdx::tuple`, in C++17 a `std::tuple`. + NOTE: `stdx::for_each` is `constexpr` in C++20 and later, because it uses https://en.cppreference.com/w/cpp/utility/functional/invoke[`std::invoke`]. `stdx::for_each_n` is just like `stdx::for_each`, but instead of taking two iterators to delimit the input range, it takes an iterator and size. +Correspondingly, its return value includes the advanced `InputIt`. [source,cpp] ---- template constexpr auto for_each_n(InputIt first, Size n, - Operation op, InputItN... first_n) -> Operation; + Operation op, InputItN... first_n); + -> for_each_result; +---- + +=== `for_each_butlast`, `for_each_butlastn` + +`stdx::for_each_butlast` is like `for_each` but omits the last element of +each range. + +[source,cpp] +---- +template +constexpr auto for_each_butlast(FwdIt first, FwdIt last, + Operation op, FwdItN... first_n) + -> for_each_result; +---- + +`stdx::for_each_butlastn` omits the last `n` elements of each range. + +[source,cpp] +---- +template +constexpr auto for_each_butlastn(FwdIt first, FwdIt last, N n, + Operation op, FwdItN... first_n) + -> for_each_result; +---- + +NOTE: `for_each_butlast` and `for_each_butlastn` are defined for forward +iterators (or stronger) only. + +=== `initial_medial_final` + +`initial_medial_final` iterates over a range, calling one function +for the initial element, another function for each of the medial elements, and a third +function for the final element. + +[source,cpp] +---- +template +CONSTEXPR_INVOKE auto initial_medial_final(FwdIt first, FwdIt last, IOp iop, + MOp mop, FOp fop) + -> for_each_result; ---- +If the range is only two elements, there are no medial elements, so `mop` is not +called. If the range is only one element, `fop` is not called. And if the range +is empty, no functions are called. === `transform` and `transform_n` diff --git a/include/stdx/algorithm.hpp b/include/stdx/algorithm.hpp index 1f29e8a..d6e60a9 100644 --- a/include/stdx/algorithm.hpp +++ b/include/stdx/algorithm.hpp @@ -1,11 +1,13 @@ #pragma once #include +#include #if __cplusplus >= 202002L #include #else #include #endif +#include namespace stdx { inline namespace v1 { @@ -48,25 +50,90 @@ CONSTEXPR_INVOKE auto transform_n(InputIt first, Size n, OutputIt d_first, return {d_first, first, first_n...}; } +template +using for_each_result = detail::result_tuple_t; + template CONSTEXPR_INVOKE auto for_each(InputIt first, InputIt last, Operation op, - InputItN... first_n) -> Operation { + InputItN... first_n) + -> for_each_result { while (first != last) { std::invoke(op, *first, *first_n...); static_cast(++first), (static_cast(++first_n), ...); } - return op; + return {op, first_n...}; } template CONSTEXPR_INVOKE auto for_each_n(InputIt first, Size n, Operation op, - InputItN... first_n) -> Operation { + InputItN... first_n) + -> for_each_result { while (n-- > 0) { std::invoke(op, *first, *first_n...); static_cast(++first), (static_cast(++first_n), ...); } - return op; + return {op, first, first_n...}; +} + +namespace detail { +template +CONSTEXPR_INVOKE auto for_each_butlastn(std::forward_iterator_tag, FwdIt first, + FwdIt last, N n, Operation op, + FwdItN... first_n) + -> for_each_result { + auto adv_it = first; + for (auto i = N{}; i < n; ++i) { + if (adv_it == last) { + break; + } + ++adv_it; + } + + while (adv_it != last) { + std::invoke(op, *first, *first_n...); + static_cast(++first), (static_cast(++first_n), ...); + ++adv_it; + } + return {op, first, first_n...}; +} + +template +CONSTEXPR_INVOKE auto for_each_butlastn(std::random_access_iterator_tag, + RandIt first, RandIt last, N n, + Operation op, RandItN... first_n) { + auto const sz = std::distance(first, last); + return for_each_n(first, sz - static_cast(n), op, first_n...); +} +} // namespace detail + +template +CONSTEXPR_INVOKE auto for_each_butlastn(FwdIt first, FwdIt last, N n, + Operation op, FwdItN... first_n) { + return detail::for_each_butlastn( + typename std::iterator_traits::iterator_category{}, first, last, + n, op, first_n...); +} + +template +CONSTEXPR_INVOKE auto for_each_butlast(FwdIt first, FwdIt last, Operation op, + FwdItN... first_n) { + return for_each_butlastn(first, last, 1, op, first_n...); +} + +template +CONSTEXPR_INVOKE auto initial_medial_final(FwdIt first, FwdIt last, IOp iop, + MOp mop, FOp fop) + -> for_each_result { + if (first != last) { + iop(*first); + auto [op, it] = for_each_butlast(++first, last, mop); + if (it != last) { + fop(*it); + } + return {iop, op, fop}; + } + return {iop, mop, fop}; } #undef CONSTEXPR_INVOKE diff --git a/include/stdx/tuple.hpp b/include/stdx/tuple.hpp index cef0692..26298cb 100644 --- a/include/stdx/tuple.hpp +++ b/include/stdx/tuple.hpp @@ -105,11 +105,10 @@ template struct element { T value; private: - [[nodiscard]] friend constexpr auto operator==(element const &, - element const &) + [[nodiscard]] friend auto operator==(element const &x, element const &y) -> bool = default; - [[nodiscard]] friend constexpr auto operator<=>(element const &, - element const &) = default; + [[nodiscard]] friend auto operator<=>(element const &, + element const &) = default; }; template struct fold_helper { diff --git a/test/algorithm.cpp b/test/algorithm.cpp index 70890ce..caeebb9 100644 --- a/test/algorithm.cpp +++ b/test/algorithm.cpp @@ -5,6 +5,7 @@ #include #include +#include #include TEST_CASE("unary transform", "[algorithm]") { @@ -49,10 +50,28 @@ TEST_CASE("n-ary transform_n", "[algorithm]") { TEST_CASE("n-ary for_each", "[algorithm]") { auto const input = std::array{1, 2, 3, 4}; auto output = decltype(input){}; - stdx::for_each( + auto [op, i1, i2] = stdx::for_each( std::cbegin(input), std::cend(input), - [it = std::begin(output)](auto n) mutable { *it++ = n + 1; }); - CHECK(output == std::array{2, 3, 4, 5}); + [it = std::begin(output)](auto... ns) mutable { + *it++ = (0 + ... + ns); + }, + std::cbegin(input), std::cbegin(input)); + CHECK(i1 == std::cend(input)); + CHECK(i2 == std::cend(input)); + CHECK(output == std::array{3, 6, 9, 12}); +} + +TEST_CASE("unary for_each_n", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto sum = 0; + auto f = [&sum, i = 0](auto n) mutable { + sum += n; + return ++i; + }; + auto [op, i] = stdx::for_each_n(std::cbegin(input), 2, f); + CHECK(i == std::next(std::cbegin(input), 2)); + CHECK(sum == 3); + CHECK(op(42) == 3); } TEST_CASE("n-ary for_each_n", "[algorithm]") { @@ -60,6 +79,165 @@ TEST_CASE("n-ary for_each_n", "[algorithm]") { auto output = decltype(input){}; stdx::for_each_n( std::cbegin(input), std::size(input), - [it = std::begin(output)](auto n) mutable { *it++ = n + 1; }); - CHECK(output == std::array{2, 3, 4, 5}); + [it = std::begin(output)](auto... ns) mutable { + *it++ = (0 + ... + ns); + }, + std::cbegin(input), std::cbegin(input)); + CHECK(output == std::array{3, 6, 9, 12}); +} + +TEST_CASE("for_each_butlastn (random access)", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto sum = 0; + auto [_, it] = stdx::for_each_butlastn(std::cbegin(input), std::cend(input), + 2, [&](auto n) { sum += n; }); + CHECK(sum == 3); + CHECK(it == std::next(std::cbegin(input), 2)); +} + +TEST_CASE("for_each_butlastn (forward)", "[algorithm]") { + auto const input = std::forward_list{1, 2, 3, 4}; + auto sum = 0; + auto [_, it] = stdx::for_each_butlastn(std::cbegin(input), std::cend(input), + 2, [&](auto n) { sum += n; }); + CHECK(sum == 3); + CHECK(it == std::next(std::cbegin(input), 2)); +} + +TEST_CASE("for_each_butlast (random access)", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto sum = 0; + auto [_, it] = stdx::for_each_butlast(std::cbegin(input), std::cend(input), + [&](auto n) { sum += n; }); + CHECK(sum == 6); + CHECK(it == std::next(std::cbegin(input), 3)); +} + +TEST_CASE("for_each_butlast (forward)", "[algorithm]") { + auto const input = std::forward_list{1, 2, 3, 4}; + auto sum = 0; + auto [_, it] = stdx::for_each_butlast(std::cbegin(input), std::cend(input), + [&](auto n) { sum += n; }); + CHECK(sum == 6); + CHECK(it == std::next(std::cbegin(input), 3)); +} + +TEST_CASE("n-ary for_each_butlastn", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto sum = 0; + stdx::for_each_butlastn( + std::cbegin(input), std::cend(input), 2, + [&](auto... ns) { sum += (0 + ... + ns); }, std::cbegin(input), + std::cbegin(input)); + CHECK(sum == 9); +} + +TEST_CASE("n-ary for_each_butlast", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto sum = 0; + stdx::for_each_butlast( + std::cbegin(input), std::cend(input), + [&](auto... ns) { sum += (0 + ... + ns); }, std::cbegin(input), + std::cbegin(input)); + CHECK(sum == 18); +} + +TEST_CASE("for_each_butlast (zero size, random access)", "[algorithm]") { + auto const input = std::array{}; + auto sum = 0; + stdx::for_each_butlast(std::cbegin(input), std::cend(input), + [&](auto n) { sum += n; }); + CHECK(sum == 0); +} + +TEST_CASE("for_each_butlast (zero size, forward)", "[algorithm]") { + auto const input = std::forward_list{}; + auto sum = 0; + stdx::for_each_butlast(std::cbegin(input), std::cend(input), + [&](auto n) { sum += n; }); + CHECK(sum == 0); +} + +TEST_CASE("for_each_butlastn (limit == n, random access)", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto sum = 0; + stdx::for_each_butlastn(std::cbegin(input), std::cend(input), 4, + [&](auto n) { sum += n; }); + CHECK(sum == 0); +} + +TEST_CASE("for_each_butlastn (limit == n, forward)", "[algorithm]") { + auto const input = std::forward_list{1, 2, 3, 4}; + auto sum = 0; + stdx::for_each_butlastn(std::cbegin(input), std::cend(input), 4, + [&](auto n) { sum += n; }); + CHECK(sum == 0); +} + +TEST_CASE("for_each_butlastn (limit > n, random access)", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto sum = 0; + stdx::for_each_butlastn(std::cbegin(input), std::cend(input), 5, + [&](auto n) { sum += n; }); + CHECK(sum == 0); +} + +TEST_CASE("for_each_butlastn (limit > n, forward)", "[algorithm]") { + auto const input = std::forward_list{1, 2, 3, 4}; + auto sum = 0; + stdx::for_each_butlastn(std::cbegin(input), std::cend(input), 5, + [&](auto n) { sum += n; }); + CHECK(sum == 0); +} + +TEST_CASE("initial_medial_final", "[algorithm]") { + auto const input = std::array{1, 2, 3, 4}; + auto first = 0; + auto sum = 0; + auto last = 0; + stdx::initial_medial_final( + std::cbegin(input), std::cend(input), [&](auto n) { first += n; }, + [&](auto n) { sum += n; }, [&](auto n) { last += n; }); + CHECK(first == 1); + CHECK(sum == 5); + CHECK(last == 4); +} + +TEST_CASE("initial_medial_final (no body)", "[algorithm]") { + auto const input = std::array{1, 4}; + auto first = 0; + auto sum = 0; + auto last = 0; + stdx::initial_medial_final( + std::cbegin(input), std::cend(input), [&](auto n) { first += n; }, + [&](auto n) { sum += n; }, [&](auto n) { last += n; }); + CHECK(first == 1); + CHECK(sum == 0); + CHECK(last == 4); +} + +TEST_CASE("initial_medial_final (single element)", "[algorithm]") { + auto const input = std::array{1}; + auto first = 0; + auto sum = 0; + auto last = 0; + stdx::initial_medial_final( + std::cbegin(input), std::cend(input), [&](auto n) { first += n; }, + [&](auto n) { sum += n; }, [&](auto n) { last += n; }); + CHECK(first == 1); + CHECK(sum == 0); + CHECK(last == 0); +} + +TEST_CASE("initial_medial_final (empty range)", "[algorithm]") { + auto const input = std::array{}; + auto first = 0; + auto sum = 0; + auto last = 0; + stdx::initial_medial_final( + std::cbegin(input), std::cend(input), [&](auto n) { first += n; }, + [&](auto n) { sum += n; }, [&](auto n) { last += n; }); + CHECK(first == 0); + CHECK(sum == 0); + CHECK(last == 0); } diff --git a/test/tuple.cpp b/test/tuple.cpp index a026395..5b7ebc3 100644 --- a/test/tuple.cpp +++ b/test/tuple.cpp @@ -239,7 +239,7 @@ TEST_CASE("equality comparable", "[tuple]") { REQUIRE(t == t); REQUIRE(t != stdx::tuple{5, 11}); - static_assert(t == t); // NOLINT(misc-redundant-expression) + static_assert(t == stdx::tuple{5, 10}); static_assert(t != stdx::tuple{5, 11}); }