2

Using g++ with -std=C++20 or later, the following compiles (assuming vec is a vector of appropriate type):

auto isEven = [](auto i) { return i % 2 == 0; }
auto filtered = vec | std::views::filter(isEven);
auto minEven = std::ranges::min_element(filtered);

but the following does not (wrong number of arguments to std::ranges::__min_element_fn):

auto isEven = [](auto i) { return i % 2 == 0; }
auto minEven = vec | std::views::filter(isEven) | std::ranges::min_element();

What is the rationale here? How do I know which of the spiffy range-related facilities can be incorporated in a pipe? (The latter is what I intuitively wrote; conceptually it would seem to be the "new ranges way" to do this.)

Glen Whitney
  • 446
  • 2
  • 12
  • 1
    You can kind of know it based on the namespace. `ranges` usually requires `ranges::XXX(some_range)`, whereas `views` can usually be used with `some_view | views::XXX`. – Ranoiaetep Nov 27 '22 at 15:43
  • And here's a related article about how this confusion could be [potentially solved with UFCS and **`operator |>`**](https://brevzin.github.io/c++/2019/08/22/ufcs-custom-extension/#extension) – Ranoiaetep Nov 27 '22 at 15:51
  • 1
    And here's another SO post: [Why do C++20 ranges not provide only pipe syntax?](https://stackoverflow.com/questions/59240435/why-do-c20-ranges-not-provide-only-pipe-syntax) – Ranoiaetep Nov 27 '22 at 15:55
  • @Ranoiaetep: Since UFCS is DOA, there's really no reason to bring it up. – Nicol Bolas Nov 27 '22 at 16:00
  • @NicolBolas I understand that UFCS is pretty dead, but isn't the idea of chaining free functions with an operator the gist of it? Also the conclusion of that article is to port a new operator from other functional languages. – Ranoiaetep Nov 27 '22 at 16:13

1 Answers1

6

In C++20, things are simple. All views can be piped. No algorithms can be piped. If it is in the views namespace, then it can be piped, and any view can be piped against a range. That is, if at least one of the operands of | comes from the views namespace, and the other operand is a range, then it can be piped. Otherwise it cannot.

Another way to remember it is that pipes create ranges. min_element doesn't create a range; it just finds a particular element of a range. Its result is not a range, so it's not something that can be piped.

Later versions have decided to just pipe or not pipe things based on whatever seems most expedient at the time. There is no rhyme or reason, it's just whatever proposals are fed in and accepted. ranges::to gets to be piped because the author of it proposed it with piping and the committee accepted it as such. No version of the proposal attempted to rationalize why a pipeable object was in the ranges namespace. That's just how it was proposed.

Trying to be consistent on what can and cannot be piped is clearly not a goal of the committee here. Maybe general range algorithms will be pipeable in the future. Maybe not.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Neither of these criteria would seem to be valid for C++23: std::ranges::to is not in the views namespace, and does not return a range, yet it can be piped (per https://en.cppreference.com/w/cpp/ranges/to). Hence my confusion. Is there any more concrete rationale/distinction? Or could/should we hope/expect that the inability to pipe into algorithms will be improved in future C++ revisions? Acknowledging there will not be UFCS, the point from Ranoiatep's link in the comments is valid: `vec | filter(pred) | min_element` reads much more easily/logically than `min_element(vec | filter(pred))`. – Glen Whitney Nov 27 '22 at 16:52
  • @GlenWhitney: 1) Your question is tagged C++20, so that's what my answer is based on. 2) I can't help it if the committee does something silly in later versions. The distinction was clear, and then they muddled it. Because expediency I guess. – Nicol Bolas Nov 27 '22 at 17:01
  • @GlenWhitney: "*reads much more easily/logically*" To me, the important part of that expression is that you're performing `min_element`, which returns an iterator. I have to read to the end of your expression to figure that out, whereas with the function-call version, it puts the important part (the core thing you're doing) front-and-center. – Nicol Bolas Nov 27 '22 at 17:04
  • I attempted to make the scope clear with the beginning of the question "C++20 and on". I _think_ someone else added the c++20 tag, which I've removed since it seems it was misleading. Ultimately C++ will reflect the later standards, so it seems achieving clarity as to what can and can't be piped will be important, especially given your comment that it's now "muddled". Finally, as to the desirable order of clauses, I agree it depends. It may be nice to emphasize the min by putting it first in a function call, but with a longer chain of piped operations, it might well be more readable min-last. – Glen Whitney Nov 27 '22 at 17:22
  • @GlenWhitney: "*so it seems achieving clarity as to what can and can't be piped will be important*" Well, then the committee has decided that there shall be no clarity. That they will arbitrarily decide what is and is not pipeable based on whatever seemed good at the time. This is a body of individuals, not a single person with clarity of vision. – Nicol Bolas Nov 27 '22 at 17:26
  • I certainly do not mean to have irked you. I am just trying to understand if there is a rationale/vision that is being worked toward, given that the pipes didn't seem to be working the way I thought/expected they would. Hence my asking this question. I appreciate your comments and thoughts on the topic. But if ranges and pipes currently are truly in a muddled state, then aren't discussions like this the beginnings of grass roots that could lead to them being improved/clarified in the future? – Glen Whitney Nov 27 '22 at 17:33