为何使用std::ranges::elements_of配合co_yield无法复制元素?
为什么co_yield elements_of(v)无法工作
std::generator<int>的promise类型虽然提供了yield_value(const T&)重载(用于拷贝左值并以T&&形式对外提供),但std::ranges::elements_of的设计逻辑是直接转发范围元素的原始值类别,不会自动为左值元素触发拷贝逻辑。
当你传入elements_of(v)时,范围的引用类型是int&,而std::generator<int>要求yield的元素类型是int&&,左值引用无法隐式转换为右值引用,因此编译失败。标准库没有为elements_of提供类似单个左值的拷贝重载,是因为elements_of的核心设计目标是高效转发元素,避免隐式拷贝带来的不必要性能开销——这符合C++“不付出未使用代价”的设计哲学,要求开发者显式表达拷贝意图。
最佳解决方案
方案1:用views::transform显式拷贝元素
通过std::views::transform为每个元素创建拷贝,生成一个输出右值的视图,再配合elements_of使用:
std::generator<int> works() { std::vector<int> v = { 1, 2, 3 }; // 显式拷贝每个元素,视图返回临时对象(右值) co_yield std::ranges::elements_of(v | std::views::transform([](const auto& elem) { return elem; })); }
这个方案通用且简洁,不管原vector是const还是非const,也不管T是否支持移动,都能正常工作——它通过拷贝生成新的临时对象,完美匹配std::generator<int>的T&&要求。
方案2:自定义copy_elements_of适配器
如果你希望用法更接近原生elements_of,可以自定义一个适配器,内部自动处理元素拷贝:
#include <coroutine> #include <ranges> template<std::ranges::input_range R> struct copy_elements_of { R&& range; }; // 推导指引 template<std::ranges::input_range R> copy_elements_of(R&&) -> copy_elements_of<R>; // 为std::generator的promise提供await_transform重载 template<typename Promise, std::ranges::input_range R> requires requires(Promise& p, const std::ranges::range_value_t<R>& val) { p.yield_value(val); } auto await_transform(Promise& promise, copy_elements_of<R> elem) { struct awaiter { Promise& promise; R&& range; std::ranges::iterator_t<R> it; std::ranges::sentinel_t<R> end; bool await_ready() noexcept { return it == end; } void await_suspend(std::coroutine_handle<>) noexcept {} void await_resume() { for (; it != end; ++it) { // 调用promise的yield_value(const T&),拷贝元素 promise.yield_value(*it); } } }; return awaiter{ promise, std::forward<R>(elem.range), std::ranges::begin(elem.range), std::ranges::end(elem.range) }; } // 用法示例 std::generator<int> works() { std::vector<int> v = { 1, 2, 3 }; co_yield copy_elements_of(v); }
这个适配器的行为和你期望的一致:批量yield时自动拷贝元素,用法和elements_of完全相同,同时保留了原生elements_of的简洁性。
方案3:显式遍历拷贝(备选)
如果不想用视图或自定义适配器,显式遍历并拷贝元素也是可行的,虽然不如前两种方案简洁,但逻辑直观:
std::generator<int> works() { std::vector<int> v = { 1, 2, 3 }; for (const auto& elem : v) { co_yield elem; // 调用yield_value(const int&),拷贝元素 } }
这里不需要std::move,直接yield左值引用即可触发promise的拷贝重载,符合你的需求。
关于elements_of的适用场景
elements_of并非仅适用于产生右值的范围,它的核心作用是批量转发范围元素。如果你的范围本身产生右值(比如临时容器、views::as_rvalue的结果),它可以直接转发这些右值;如果范围产生左值,你需要显式处理拷贝或移动逻辑——这是标准库刻意设计的,避免隐式操作带来的性能陷阱。
内容的提问来源于stack exchange,提问作者Nikola Benes

