Search code examples
c++c++20range-v3

Can I pipe to range-v3 accumulate?


I found older questions from 3y ago that say that in general it is not possible, but I would really like to pipe to accumulate since it is quite nice in some cases, for example this:

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

So I wonder if something has been added to range-v3/C++20 ranges that enables me to do this.


Solution

  • No.

    The only things you can pipe into are range adapters -- algorithms that take in a range and produce a range. Algorithms that take in a range and return a single object (also known as catamorphisms) are not pipeable in range-v3 or C++20 ranges.

    You have to write it this way:

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

    As to why accumulate and similar algorithms will struggle to ever be |-able. Consider that we want algo(rng, x) and rng | algo(x) to mean the same thing. Further, consider that the "total call" algo(rng, x) can be fully constrained (since you have all the information) while the "partial call" algo(x) basically has to be completely unconstrained in all but rare circumstances... basically roughly taking auto&&...

    The problem is we necessarily run into ambiguities when the second argument, x, can also be a range. How do you distinguish between the intent being a total call or a partial call?

    Here's an example using string:

    accumulate("hello"s, ""s)
    

    This is a total call, that uses the default binary operator + - which is string concatenation. What this does is iterate over the elements of the range of chars and add them one by one to the initial empty string. This is an inefficient, yet correct, way to copy a string. You end up with the value "hello"s.

    What about its equivalent pipe version?

    "hello"s | accumulate(""s)
    

    What does the right-hand side mean? Can accumulate(""s) be considered a total call? Yes it can! The defaulted 2nd argument would be char() and the defaulted third argument would be plus(), this works fine, and so the value of accumulate(""s) is the integer 0 - making the whole expression ill-formed because there's no operator|(string, int).

    How do you make this work with accumulate?