There isn't really any inherent design reason why some sets of algorithms do have pipe support (views, actions, and to
- although C++20 only has views so far) and others don't (everything else in <algorithm>
). Piping into for_each
makes just as much sense as piping into filter
, it's just that range-v3 (and, consequently, C++20 Ranges) only did so for some things and not others.
As of now, there is no opt-in mechanism to make something "pipeable" in the Ranges sense (see P2387 to change that). But once that paper, or some variant of it, gets approved, it would become easy to write an algorithm adapter that simply makes algorithms pipeable. So that you could do, for instance, this:
elements
| std::views::filter([](auto i) { return i % 2 == 0; })
| adaptor(std::ranges::for_each)([](auto e) {std::cout << e << std::endl; });
Demo (note that this depends on libstdc++ internals at the moment).
One issue with the library machinery approach to this problem is ambiguity - this relies on deciding that if f(x, y)
is invokable that the intent is to make it a full call rather than a partial one. It's certainly possible for some algorithms that you may not be able to distinguish between a partial and full call in this sense (which itself may be one motivation for not having them be implicitly pipeable). This is certainly a known problem with some views already (zip
, concat
, and join
taking a delimiter are examples) and is itself one of the motivations for having a language solution to this problem (e.g. P2011).
Of course that only affects whether you could write | some_algo(args...)
, the opt-in mechanism that I'm describing here could just as easily have been spelled | partial(ranges::for_each, args...)
instead of | adaptor(ranges::for_each)(args...)
, and then there'd be no ambiguity. It's just a weirder spelling in my opinion, which is why I showed it the way I did.