为什么每个循环的 c++ 接受 r 值但 std::ranges 不接受?
Why does c++ for each loops accept r-values but std::ranges do not?
像这样的语句编译没有错误:
for (int i : std::vector<int>({0, 1}))
std::cout << i;
但是像这样的声明不会:
std::vector<int>({0, 1}) |
std::views::filter([](int i) { return true; });
为什么每个循环都允许 r 值但 std::range 管道中不允许?有没有一种方法可以使类似第二个的东西工作,而无需声明变量?
for-loop 工作正常,因为 vector<int>
(右值)在评估循环时有效。
第二个代码片段有 2 个问题:
- 谓词必须return一个
bool
- 您在视图对象中使用的
vector<int>
在评估时处于悬空状态,因此编译器不接受它。
如果不是 vector<int>
右值,对象不能悬挂(左值)或将引用保存到不能悬挂的其他对象中,这将起作用。
例如:
auto vec = std::vector<int>({0, 1});
auto vw = vec | std::ranges::views::filter([](int i) { std::cout << i; return true; });
或者你可以传递一个引用了 non-dangling vector<int>
对象的右值,比如 std::span
:
auto vec = std::vector<int>({0, 1});
auto vw = span{vec} | std::ranges::views::filter([](int i) { std::cout << i; return true; })
这里,std::span
仍然是一个右值,但是编译器接受了它,因为std::span
的作者已经为它创建了一个例外。
对于引用其他内容(通常是视图)的自定义类型,您可以通过创建模板变量的特化来创建自己的异常 enable_borrowed_range
。
在矢量的情况下,它看起来像这样(但不要这样做!):
template<> // don't do this
inline constexpr bool ranges::enable_borrowed_range<std::vector<int>> = true;
您的代码现在可以编译,但它会触发未定义的行为,因为一旦对视图求值,向量就会悬空。
这适用于最新的 MSVC STL 和 libstdc++ (demo), thanks to P2415R2。
在 P2415R2 之前,filter_view
始终通过引用(通过 ref_view
)存储基础 vector
。存储 vector
的副本被认为是不明智的。因为视图应该是廉价复制、廉价移动和廉价销毁。存储 vector
的副本会导致意外的性能损失。
并且由于生命周期问题,引用右值没有意义 vector
。
但 P2415R2 指出,视图也可以是 non-copyable,同时移动成本和销毁成本也相当低。
因此 P2415R2 引入了 owning_view
,一个存储范围右值副本的 non-copyable 视图,并使标准范围适配器使用该视图。
最终结果是,从 vector
右值创建 filter_view
适用于实现 P2415R2 的标准库实现。
像这样的语句编译没有错误:
for (int i : std::vector<int>({0, 1}))
std::cout << i;
但是像这样的声明不会:
std::vector<int>({0, 1}) |
std::views::filter([](int i) { return true; });
为什么每个循环都允许 r 值但 std::range 管道中不允许?有没有一种方法可以使类似第二个的东西工作,而无需声明变量?
for-loop 工作正常,因为 vector<int>
(右值)在评估循环时有效。
第二个代码片段有 2 个问题:
- 谓词必须return一个
bool
- 您在视图对象中使用的
vector<int>
在评估时处于悬空状态,因此编译器不接受它。
如果不是 vector<int>
右值,对象不能悬挂(左值)或将引用保存到不能悬挂的其他对象中,这将起作用。
例如:
auto vec = std::vector<int>({0, 1});
auto vw = vec | std::ranges::views::filter([](int i) { std::cout << i; return true; });
或者你可以传递一个引用了 non-dangling vector<int>
对象的右值,比如 std::span
:
auto vec = std::vector<int>({0, 1});
auto vw = span{vec} | std::ranges::views::filter([](int i) { std::cout << i; return true; })
这里,std::span
仍然是一个右值,但是编译器接受了它,因为std::span
的作者已经为它创建了一个例外。
对于引用其他内容(通常是视图)的自定义类型,您可以通过创建模板变量的特化来创建自己的异常 enable_borrowed_range
。
在矢量的情况下,它看起来像这样(但不要这样做!):
template<> // don't do this
inline constexpr bool ranges::enable_borrowed_range<std::vector<int>> = true;
您的代码现在可以编译,但它会触发未定义的行为,因为一旦对视图求值,向量就会悬空。
这适用于最新的 MSVC STL 和 libstdc++ (demo), thanks to P2415R2。
在 P2415R2 之前,filter_view
始终通过引用(通过 ref_view
)存储基础 vector
。存储 vector
的副本被认为是不明智的。因为视图应该是廉价复制、廉价移动和廉价销毁。存储 vector
的副本会导致意外的性能损失。
并且由于生命周期问题,引用右值没有意义 vector
。
但 P2415R2 指出,视图也可以是 non-copyable,同时移动成本和销毁成本也相当低。
因此 P2415R2 引入了 owning_view
,一个存储范围右值副本的 non-copyable 视图,并使标准范围适配器使用该视图。
最终结果是,从 vector
右值创建 filter_view
适用于实现 P2415R2 的标准库实现。