xmllmx
xmllmx

Reputation: 42343

What is std::views::all introduced for in C++20?

#include <vector>
#include <ranges>

int main()
{
    auto v = std::vector{1, 2, 3, 4};
    v | std::views::drop(2); // ok
    std::views::all(v) | std::views::drop(2); // also ok
}

Successfully compiled with g++11 -std=c++20. But I cannot tell any difference between v | std::views::drop(2) and std::views::all(v) | std::views::drop(2).

So, my question is:

What is std::views::all introduced for in C++20?

Upvotes: 25

Views: 14091

Answers (3)

Weijun Zhou
Weijun Zhou

Reputation: 4896

std::views::all can act as an intermediate layer to avoid unnecessary copies when dealing some class templates which cannot treat references properly. An example is the use of std::initializer_list<T> where T is a std::views::all over an existing container. It can be useful to implement something functional similar to std::views::concat in C++26. An example code is shown below,

#include <vector>
#include <iostream>
#include <ranges>

struct A{
    A(int i):i(i){}
    A(const A& other){
        std::cout << "Copy\n";
        i = other.i;
    }
    A(A&& other){
        std::cout << "Move\n";
        i = other.i;
    }
    A& operator=(const A& other){
        std::cout << "Copy assignment\n";
        i = other.i;
        return *this;
    }
    A& operator=(A&& other){
        std::cout << "Move assignment\n";
        i = other.i;
        return *this;
    }
    int i;
};

int main(){
    std::vector<A> avec1{1,2,3};
    std::vector<A> avec2{4,5,6};
    std::cout << "After vector construction\n";
    auto vecs = {std::views::all(avec1), std::views::all(avec2)};
    for(const auto& el: std::views::join(vecs)){
        std::cout << el.i << ",";
    }
    std::cout << '\n';
}

Here std::views::all is used to provide a way to iterate over the underlying vector without unnecessary copies. std::views::all is necessary because auto vecs={avec1, avec2}; would copy both avec1 and avec2 and std::initializer_list<T> doesn't work when T is a reference-type. Another choice here is std::span, but std::views::all has the benefit of also working for other types of containers.

Note that the underlying types of the initializer_list still need to be of the same type in this case and hence std::views::concat in C++26 is still necessary to handle different types of views.

Demo: https://godbolt.org/z/aj16a16f6

Upvotes: 0

MarkB
MarkB

Reputation: 1988

You may want your interface to return a range instead of the underlying container. In the example below, container_api exposes member methods (i.e. of std::vector) that aren't part of a view (e.g. rbegin(), capacity(), max_size()). range_api exposes operator bool, which isn't part of a vector.

Another important difference is that the return type of range_api is an object and not a reference. This may prevent unintentional copies from users thinking they are getting a range when the actual interface is returning a reference to a container.

class Foo {
public:
     Foo() { ... }
     const auto& container_api() const { return m_vec; }
     auto range_api() const { return std::views::all(m_vec); }
private:
     std::vector<int> m_vec;
};

void some_fn(const Foo& foo)
{
   auto rng = foo.container_api();   // unwanted copy!
   ...
}

Upvotes: 3

Barry
Barry

Reputation: 303057

But I cannot tell any difference between v | std::views::drop(2) and std::views::all(v) | std::views::drop(2).

Indeed, there is no difference between the two - because v | views::drop(2) already means views::all(v) | views::drop(2).

views::all is an implementation detail of Ranges to ensure that range adaptors always adapt views (not ranges). All that views::all(v) does is ensure that the result is a View, which is to say (from [range.all]):

Given a subexpression E, the expression views​::​all(E) is expression-equivalent to:

  • decay-copy(E) if the decayed type of E models view.
  • Otherwise, ref_­view{E} if that expression is well-formed.
  • Otherwise, subrange{E}.

In your case, v is a vector<int>, which does not model view. But it is an lvalue, so ref_view{v} would be well-formed, so that's what happens.

All the adaptors use views::all internally. For instance, drop_view has the following deduction guide:

template <class R>
drop_view(R&&, range_difference_t<R>) -> drop_view<views::all_t<R>>;

So if you wrote drop_view(v, 2) (and you should never use meow_view directly, always use views::meow), that would itself invoke views::all for you.

Upvotes: 28

Related Questions