C++20 中引入 std::views::all 的目的是什么?

问题描述 投票:0回答:3
#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
}

g++11 -std=c++20
编译成功。但我无法区分
v | std::views::drop(2)
std::views::all(v) | std::views::drop(2)
之间的区别。

所以,我的问题是:

C++20 中引入的std::views::all

是什么?
    

c++ standards c++20 std std-ranges
3个回答
27
投票
但我无法区分

v | std::views::drop(2)

std::views::all(v) | std::views::drop(2)
 之间的区别。

确实,两者之间没有区别 - 因为

v | views::drop(2)

 已经意味着 
views::all(v) | views::drop(2)

views::all

是范围的实现细节,以确保范围适配器始终适应
视图(而不是范围)。 views::all(v)
 所做的只是确保结果是一个视图,也就是说(来自 
[range.all]):

给定一个子表达式

E

,表达式 
views​::​all(E)
 的表达式等价于:

  • decay-copy(E)
     如果
    E
    型号的腐烂类型
    view
  • 否则,
  • ref_­view{E}
    (如果该表达式格式正确)。
  • 否则,
  • subrange{E}
在您的情况下,

v

是一个
vector<int>
,它不会模拟
view
。但它是一个左值,所以 
ref_view{v}
 格式良好,所以就会发生这样的情况。

所有适配器内部都使用

views::all

。例如
drop_view
有如下推演指南:

template <class R> drop_view(R&&, range_difference_t<R>) -> drop_view<views::all_t<R>>;
因此,如果您写了 

drop_view(v, 2)

(并且您永远不应该直接使用 
meow_view
,始终使用 
views::meow
),那么它本身就会为您调用 
views::all


2
投票
您可能希望接口返回一个范围而不是底层容器。 在下面的示例中,

container_api

 公开了不属于视图一部分的成员方法(即 std::vector 的)(例如 rbegin()、capacity()、max_size())。  
range_api
 暴露 
operator bool
,它不是向量的一部分。

另一个重要的区别是

range_api

 的返回类型是对象而不是引用。  当实际接口返回对容器的引用时,这可以防止用户认为自己获得了范围而无意中进行复制。

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! ... }
    

0
投票

std::views::all

可以充当中间层,以避免在处理一些无法正确处理引用的类模板时不必要的复制。一个示例是使用 
std::initializer_list<T>
,其中 
T
 是现有容器上的 
std::views::all
。在 C++26 中实现类似于 
std::views::concat
 的功能可能很有用。示例代码如下所示,

#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'; }
这里 

std::views::all

 用于提供一种迭代底层向量而无需不必要的副本的方法。 
std::views::all
 是必需的,因为 
auto vecs={avec1, avec2};
 会复制 
avec1
avec2
,并且当 
std::initializer_list<T>
 是引用类型时,
T
 不起作用。这里的另一个选择是 
std::span
,但是 
std::views::all
 的优点是也适用于其他类型的容器。

请注意,在这种情况下,

initializer_list

 的基础类型仍然需要是相同的类型,因此 C++26 中的 
std::views::concat
 仍然需要处理不同类型的视图。

演示:

https://godbolt.org/z/aj16a16f6

© www.soinside.com 2019 - 2024. All rights reserved.