Why aren't range v3 algorithms pipeable?

799 Views Asked by At

It seems that the algorithms in ranges v3 aren't chainable, i.e:

const auto ints = std::vector<int>{1,2,1,3,1,4,1,5,1,6};
const auto num_ones = ints | ranges::count(1);

... has to be written functional style:

const auto num_ones = ranges::count(ints, 1);

Is this a design choice that only algorithms/actions which returns a new range/container are pipeable?

2

There are 2 best solutions below

0
On

Some algorithms are actually chainable and you find them in the view and/or action namespace.

But your code suggest that you have a different question actually. Why are there no algorithms with a signature allowing to end a pipe chain? I suggest the namespace reducer for such algorithms. Here is a working code example:

#include <iostream>
#include <string>
#include <vector>
#include <range/v3/all.hpp>

using namespace std;
namespace view = ranges::view;
namespace action = ranges::action;

namespace reducer {
    template <typename T>
    class count {
        T t;
        public:
        count(T t) : t(t) {}
        template <typename Left>
        T operator()(Left left) {
            return ranges::count(left, t);
        }
    };

    template <typename Left, typename T>
    int operator|(Left left, count<T> right) {
        return right(left);
    }
}

int main (int argc, char * argv[])
{
    const auto ints = std::vector<int>{1,2,1,3,1,4,1,5,1,6};
    const auto num_ones = ints | reducer::count(1);
    cout << num_ones << endl;
    return 0;
}

Eric Niebler said, that people shoot many ideas out of the hip, like we do, but do not see the profound consequences. So maybe there is something bad about the idea that we do not see. Would be great if he passes by your question and enlighten us with a comment.

Of course he was using C++11 for ranges-v3 and without typededuction for constructors this idea is harder to implement.

0
On

The output of chained views must be another view (i.e. a range). That way, you can keep chaining the result using more views.

The result of count is not a range, so it doesn't make sense to have that operation in the chain. In the hypothetical case that you were able to, you wouldn't be able to chain the result of that operation to another view.

Viewing the situation from another angle, in range-v3 views are lazily evaluated. Counting the number of elements in a range is not a lazy operation, since it requires the entire range to be evaluated to get a result. It's a different kind of operation.

The same reasoning can be applied to the other "free-standing" algorithms, like ranges::copy, ranges::sort, ranges::min_element, etc. Those should be seen as variants (or improvements) of the corresponding std algorithms, but that also accept ranges as arguments, instead of pairs of iterators.

With that being said, some of the free-standing algorithms are also available as views, where it makes sense (like the set_intersection, set_difference and set_union family of algorithms).

Edit: There are exceptions to this rule. Namely, the functions ranges::to_vector and ranges::to_, which "sink" the piped range into a std::vector (or a container of your choosing).