Skip to content

Commit 1929def

Browse files
committed
✨ 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`.
1 parent 7e1cbc7 commit 1929def

File tree

5 files changed

+311
-16
lines changed

5 files changed

+311
-16
lines changed

docs/algorithm.adoc

+53-2
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,74 @@ variadic in its inputs.
1414
----
1515
template <typename InputIt, typename Operation, typename... InputItN>
1616
constexpr auto for_each(InputIt first, InputIt last,
17-
Operation op, InputItN... first_n) -> Operation;
17+
Operation op, InputItN... first_n)
18+
-> for_each_result<Operation, InputItN...>;
1819
----
1920

21+
The return value is equivalent to a `tuple<Operation, InputItN...>`.
22+
In C\\++20 and later this is a `stdx::tuple`, in C++17 a `std::tuple`.
23+
2024
NOTE: `stdx::for_each` is `constexpr` in C++20 and later, because it uses
2125
https://en.cppreference.com/w/cpp/utility/functional/invoke[`std::invoke`].
2226

2327
`stdx::for_each_n` is just like `stdx::for_each`, but instead of taking two
2428
iterators to delimit the input range, it takes an iterator and size.
29+
Correspondingly, its return value includes the advanced `InputIt`.
2530

2631
[source,cpp]
2732
----
2833
template <typename InputIt, typename Size, typename Operation,
2934
typename... InputItN>
3035
constexpr auto for_each_n(InputIt first, Size n,
31-
Operation op, InputItN... first_n) -> Operation;
36+
Operation op, InputItN... first_n);
37+
-> for_each_result<Operation, InputIt, InputItN...>;
38+
----
39+
40+
=== `for_each_butlast`, `for_each_butlastn`
41+
42+
`stdx::for_each_butlast` is like `for_each` but omits the last element of
43+
each range.
44+
45+
[source,cpp]
46+
----
47+
template <typename FwdIt, typename Operation,
48+
typename... FwdItN>
49+
constexpr auto for_each_butlast(FwdIt first, FwdIt last,
50+
Operation op, FwdItN... first_n)
51+
-> for_each_result<Operation, FwdIt, FwdItN...>;
52+
----
53+
54+
`stdx::for_each_butlastn` omits the last `n` elements of each range.
55+
56+
[source,cpp]
57+
----
58+
template <typename FwdIt, typename Size, typename Operation,
59+
typename... FwdItN>
60+
constexpr auto for_each_butlastn(FwdIt first, FwdIt last, N n,
61+
Operation op, FwdItN... first_n)
62+
-> for_each_result<Operation, FwdIt, FwdItN...>;
63+
----
64+
65+
NOTE: `for_each_butlast` and `for_each_butlastn` are defined for forward
66+
iterators (or stronger) only.
67+
68+
=== `initial_medial_final`
69+
70+
`initial_medial_final` iterates over a range, calling one function
71+
for the initial element, another function for each of the medial elements, and a third
72+
function for the final element.
73+
74+
[source,cpp]
75+
----
76+
template <typename FwdIt, typename IOp, typename MOp, typename FOp>
77+
CONSTEXPR_INVOKE auto initial_medial_final(FwdIt first, FwdIt last, IOp iop,
78+
MOp mop, FOp fop)
79+
-> for_each_result<IOp, MOp, FOp>;
3280
----
3381

82+
If the range is only two elements, there are no medial elements, so `mop` is not
83+
called. If the range is only one element, `fop` is not called. And if the range
84+
is empty, no functions are called.
3485

3586
=== `transform` and `transform_n`
3687

include/stdx/algorithm.hpp

+71-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#pragma once
22

33
#include <functional>
4+
#include <iterator>
45
#if __cplusplus >= 202002L
56
#include <stdx/tuple.hpp>
67
#else
78
#include <tuple>
89
#endif
10+
#include <type_traits>
911

1012
namespace stdx {
1113
inline namespace v1 {
@@ -48,25 +50,90 @@ CONSTEXPR_INVOKE auto transform_n(InputIt first, Size n, OutputIt d_first,
4850
return {d_first, first, first_n...};
4951
}
5052

53+
template <typename Op, typename... Is>
54+
using for_each_result = detail::result_tuple_t<Op, Is...>;
55+
5156
template <typename InputIt, typename Operation, typename... InputItN>
5257
CONSTEXPR_INVOKE auto for_each(InputIt first, InputIt last, Operation op,
53-
InputItN... first_n) -> Operation {
58+
InputItN... first_n)
59+
-> for_each_result<Operation, InputItN...> {
5460
while (first != last) {
5561
std::invoke(op, *first, *first_n...);
5662
static_cast<void>(++first), (static_cast<void>(++first_n), ...);
5763
}
58-
return op;
64+
return {op, first_n...};
5965
}
6066

6167
template <typename InputIt, typename Size, typename Operation,
6268
typename... InputItN>
6369
CONSTEXPR_INVOKE auto for_each_n(InputIt first, Size n, Operation op,
64-
InputItN... first_n) -> Operation {
70+
InputItN... first_n)
71+
-> for_each_result<Operation, InputIt, InputItN...> {
6572
while (n-- > 0) {
6673
std::invoke(op, *first, *first_n...);
6774
static_cast<void>(++first), (static_cast<void>(++first_n), ...);
6875
}
69-
return op;
76+
return {op, first, first_n...};
77+
}
78+
79+
namespace detail {
80+
template <typename FwdIt, typename N, typename Operation, typename... FwdItN>
81+
CONSTEXPR_INVOKE auto for_each_butlastn(std::forward_iterator_tag, FwdIt first,
82+
FwdIt last, N n, Operation op,
83+
FwdItN... first_n)
84+
-> for_each_result<Operation, FwdIt, FwdItN...> {
85+
auto adv_it = first;
86+
for (auto i = N{}; i < n; ++i) {
87+
if (adv_it == last) {
88+
break;
89+
}
90+
++adv_it;
91+
}
92+
93+
while (adv_it != last) {
94+
std::invoke(op, *first, *first_n...);
95+
static_cast<void>(++first), (static_cast<void>(++first_n), ...);
96+
++adv_it;
97+
}
98+
return {op, first, first_n...};
99+
}
100+
101+
template <typename RandIt, typename N, typename Operation, typename... RandItN>
102+
CONSTEXPR_INVOKE auto for_each_butlastn(std::random_access_iterator_tag,
103+
RandIt first, RandIt last, N n,
104+
Operation op, RandItN... first_n) {
105+
auto const sz = std::distance(first, last);
106+
return for_each_n(first, sz - static_cast<decltype(sz)>(n), op, first_n...);
107+
}
108+
} // namespace detail
109+
110+
template <typename FwdIt, typename N, typename Operation, typename... FwdItN>
111+
CONSTEXPR_INVOKE auto for_each_butlastn(FwdIt first, FwdIt last, N n,
112+
Operation op, FwdItN... first_n) {
113+
return detail::for_each_butlastn(
114+
typename std::iterator_traits<FwdIt>::iterator_category{}, first, last,
115+
n, op, first_n...);
116+
}
117+
118+
template <typename FwdIt, typename Operation, typename... FwdItN>
119+
CONSTEXPR_INVOKE auto for_each_butlast(FwdIt first, FwdIt last, Operation op,
120+
FwdItN... first_n) {
121+
return for_each_butlastn(first, last, 1, op, first_n...);
122+
}
123+
124+
template <typename FwdIt, typename IOp, typename MOp, typename FOp>
125+
CONSTEXPR_INVOKE auto initial_medial_final(FwdIt first, FwdIt last, IOp iop,
126+
MOp mop, FOp fop)
127+
-> for_each_result<IOp, MOp, FOp> {
128+
if (first != last) {
129+
iop(*first);
130+
auto [op, it] = for_each_butlast(++first, last, mop);
131+
if (it != last) {
132+
fop(*it);
133+
}
134+
return {iop, op, fop};
135+
}
136+
return {iop, mop, fop};
70137
}
71138

72139
#undef CONSTEXPR_INVOKE

include/stdx/tuple.hpp

+3-4
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,10 @@ template <std::size_t Index, typename T, typename... Ts> struct element {
105105
T value;
106106

107107
private:
108-
[[nodiscard]] friend constexpr auto operator==(element const &,
109-
element const &)
108+
[[nodiscard]] friend auto operator==(element const &x, element const &y)
110109
-> bool = default;
111-
[[nodiscard]] friend constexpr auto operator<=>(element const &,
112-
element const &) = default;
110+
[[nodiscard]] friend auto operator<=>(element const &,
111+
element const &) = default;
113112
};
114113

115114
template <typename Op, typename Value> struct fold_helper {

0 commit comments

Comments
 (0)