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?
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With