#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
但我无法区分确实,两者之间没有区别 - 因为
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
。
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!
...
}
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
仍然需要处理不同类型的视图。演示: