2

Since ranges were merged into C++ 20, I've been looking through headers to see how operator| is overloaded for range views, but I can't find the right track on how or where it is implemented.


C(R) is equivalent to R | C according to https://en.cppreference.com/w/cpp/ranges, based on what I've read.

Or, V(R, F) is equivalent to R | V(F), where V is the range adaptor object, R is the viewable range, and F is any callable object including free functions.


I've already read What are the basic rules and idioms for operator overloading? and already know how to overload operators properly, but I can't find the guide on what function arguments or template arguments are needed, or the syntactical meaning, to fulfill the requirement of the said implementation.

My point here is not to replace every bit of info from range adaptors, but only its syntactical feature on adaptor ranges with overloaded operator|.

Another is that the resulting object type after the pipes must be the same as the type of container, as long as it fulfills the following concepts.

My mere idea:

template <std::ranges::viewable_range Ran, std::invocable Inv>
auto operator|(Ran cont, Inv func) {
    /* ... */ 
} 

Some Applications (supposed we already implemented the functions):

std::vector<int> vect1 {10, 3, 5, 3};
auto new_vect1 = vect1 | myfilter([](auto x){ return x % 3 == 0; });
// where new_vect1 is still `std::vector<int>`

where myfilter:

template <std::viewable_range T, std::invocable F>
decltype(auto) myfilter(T container, F func);

But I think my idea is either entirely wrong, or wrong in some aspects, and the majority of this has been missing out.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Desmond Gold
  • 1,517
  • 1
  • 7
  • 19
  • 2
    If you want to know how to implement ranges, look at the implementation. Libstdc++ and libc++ are both open source and you can see how they do it or you can look at ranges-v3, the source of us getting `std::ranges` and see how it's done pre C++20 concepts. – NathanOliver May 26 '21 at 14:03

1 Answers1

3

This is the basic structure:

template<std::ranges::viewable_range Range, other_concepts... OtherArgs>
new_range_t my_view(Range range, OtherArgs... args);

template<other_concepts... OtherArgs>
/* some type */ my_view(OtherArgs... args);

template<std::ranges::viewable_range Range, other_concepts... OtherArgs>
new_range_t operator|(Range range, /* some type */ holder);

Instead of a pair of function templates, you could do that with an object with two operator() templates.

struct my_view_t {
    template<std::ranges::viewable_range Range, other_concepts... OtherArgs>
    new_range_t operator()(Range range, OtherArgs... args);

    template<other_concepts... OtherArgs>
    /* some type */ operator()(OtherArgs... args);
} my_view;

OtherArgs... probably isn't a parameter pack, but it represents any additional parameters your view takes. If it were a parameter pack, you'd have to ensure the second argument was never a range, so that my_view(R, args...) and R | my_view(args...) isn't ambiguous as to which overload.

In the specific case where there aren't any other arguments, you can have a single object do double duty.

struct nullary_view_t {
    template<std::ranges::viewable_range Range>
    new_range_t operator()(Range range);

    template<std::ranges::viewable_range Range>
    friend new_range_t operator|(Range range, nullary_view_t);
} nullary_view;
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • `std::ranges::views::take`, for instance, seems to be taking its range by universal reference, not value. Should we not always do that? I.e. `Range&& range` instead of `Range range`? – nilo Jun 11 '23 at 20:12
  • @nilo yes, this is just a scetch – Caleth Jun 13 '23 at 07:34