6

I understand that question sounds weird, so here is a bit of context.

Recently I was disappointed to learn that map reduce in C++20 ranges does not work as one would expect i.e.

const double val = data | transform(...) | accumulate (...);

does not work, you must write it this unnatural way:

const double val = accumulate(data | transform(...));

Details can be found here and here, but it boils down to the fact that accumulate can not disambiguate between 2 different usecases.

So this got me thinking:

If C++20 required that you must use pipe for using ranges, aka you can not write

vector<int> v;
sort(v);

but you must write

vector<int> v
v|sort();

would that would solve problem of ambiguity?

And if so although unnatural to people using std::sort and other STL algorithms I wonder if in the long run that would be a better design choice.

Note: If this question is too vague feel free to vote to close, but I feel that this is a legitimate design question that can be answered in relatively unbiased way, especially if my understanding of the problem is wrong.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277
  • Which two different use-cases cannot be disambiguated? If you don't explain that, we can't understand your bottom-line question. – einpoklum Dec 08 '19 at 22:31
  • @einpoklum-reinstateMonica I provided the link, I considered it to be too verbose to be inlined: https://stackoverflow.com/questions/59130785/can-i-pipe-to-range-v3-accumulate/59131902#59131902 – NoSenseEtAl Dec 08 '19 at 22:37
  • 1
    You can sum it up in a couple of sentences IMHO. It's customary on SO to not rely that much on the contents of links. Anyway, I see what you mean; but my answer stands on general ground though. – einpoklum Dec 08 '19 at 22:40

2 Answers2

6

You need to differentiate between range algorithms and range adaptors. Algorithms are functions that perform a generic operation on a range of values. Adaptors are functions which create range views that modify the presentation of a range. Adaptors are chained by the | operator; algorithms are just regular functions.

Sometimes, the same conceptual thing can have an algorithm and adapter form. transform exists as both an algorithm and an adapter. The former stores the transformation into an output range; the latter creates a view range of the input that lazily computes the transformation as requested.

These are different tasks for different needs and uses.

Also, note that there is no sort adapter in C++20. A sort adapter would have to create a view range that somehow mixed around the elements in the source range. It would have to allocate storage for the new sequence of values (even if it's just sorting iterators/pointers/indices to the values). And the sorting would have to be done at construction time, so there would be no lazy operation taking place.

This is also why accumulate doesn't work that way. It's not a matter of "ambiguity"; it's a matter of the fundamental nature of the operation. Accumulation computes a value from a range; it does not compute a new range from an existing one. That's the work of an algorithm, not an adapter.

Some tasks are useful in algorithm form. Some tasks are useful in adapter form (you find very few zip-like algorithms). Some tasks are useful in both. But because these are two separate concepts for different purposes, they have different ways of invoking them.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    OP isn't claiming that you cannot write that. OP is positing a hypothetical design where you couldn't. – Barry Dec 08 '19 at 22:46
  • @Barry: See my last paragraph. *That's* why these are different tasks for different needs. – Nicol Bolas Dec 08 '19 at 22:47
  • 2
    I don't understand what point you're trying to make in the (what used to be the) last paragraph. range-v3 let's me write `get_data() | actions::sort` - I don't see anything about `|` that would prohibit me from doing actions like this if I so chose. – Barry Dec 08 '19 at 22:52
  • @Barry: Now you're getting into an entirely separate conversation: *actions*, which are a distinct concept from both adapters and algorithms. – Nicol Bolas Dec 08 '19 at 22:54
  • 2
    They're not separate at all? I don't actually buy that any of these things are meaningfully distinct - especially where syntax is concerned. A range adapter is a lazy algorithm and an action is an eager algorithm - where adapters and actions take and produce ranges. What's _fundamental_ about accumulate that `data | transform(f) | filter(g) | accumulate` can't be valid? It's a pretty reasonable thing to want to do, and works just fine in other languages/libraries. – Barry Dec 08 '19 at 23:02
  • @Barry: "*What's fundamental about accumulate that `data | transform(f) | filter(g) | accumulate` can't be valid?*" I generally don't like the idea that we have a bunch of things which fundamentally do conceptually different things that we're going to pretend is the same thing. `accumulate` computes a value; the others build range views. `accumulate` acts instantly on the given range; the others evaluate lazily. That's what makes them "fundamentally" different. I like the idea that `|` means "make a view of the range", not "apply arbitrary function to this other thing". – Nicol Bolas Dec 08 '19 at 23:09
4

would that would solve problem of ambiguity?

Yes.

If there's only one way to write something, that one way must be the only possible interpretation. If an algorithm "call" can only ever be a partial call to the algorithm that must be completed with a | operation with a range on the left hand side, then you'd never even have the question of if the algorithm call is partial or total. It's just always partial.

No ambiguity in that sense.

But if you went that route though, you end up with things like:

auto sum = accumulate("hello"s);

Which doesn't actually sum the chars in that string and actually is placeholder that is waiting on a range to accumulate over with the initial value "hello"s.

Barry
  • 286,269
  • 29
  • 621
  • 977