为仅可移动的一次性生成器实现for循环兼容包装器时,右值限定begin()函数的编译错误问题
嗨,这个问题其实是range-based for循环的调用逻辑和你对begin()的右值限定不匹配导致的——咱们先理清楚为什么会报错:
当你写for (auto i : iterable)的时候,range-based for循环会把iterable当作左值来处理,它会尝试调用左值版本的begin()成员函数,但你只给Iterable定义了右值限定的begin() &&,编译器找不到对应的左值版本,自然就抛出了那个"discards qualifiers"的错误。
不过你觉得右值限定begin()语义上更合理的想法是完全正确的:因为你的Generator是仅可移动、一次性的,遍历过程需要把生成器的所有权转移到迭代器里,这个操作确实应该消耗原Iterable对象,不应该允许重复遍历。那怎么解决这个问题呢?有两种比较优雅的方案:
方案一:直接用右值对象进行遍历
既然你的Iterable本来就是一次性的,那可以直接在for循环里用临时对象,或者把左值转换成右值,这样就会触发右值版本的begin():
int main() { // 直接用临时Iterable对象,本身就是右值 for (auto i : Iterable{Generator{}}) { std::cout << i << std::endl; } // 或者如果需要先保存对象,遍历的时候用std::move转成右值 // Iterable iterable{Generator{}}; // for (auto i : std::move(iterable)) { // std::cout << i << std::endl; // } return 0; }
这种方式完美契合你原本的语义:遍历完成后,原Iterable对象(不管是临时的还是被move的)里的Generator已经被转移走,无法再被遍历,完全符合一次性生成器的特性。
方案二:添加左值版本的begin(),但保留一次性语义
如果你希望支持左值对象的遍历,但又不想破坏“一次性”的语义,可以给Iterable添加左值版本的begin(),在里面同样把_gen移动到迭代器里:
class Iterable { // ... 其他成员保持不变 ... // 左值版本的begin(),同样转移_generator的所有权 Iterator begin() & { return Iterator(std::move(_gen)); // 可选:可以在这里把_gen重置为默认构造的空状态,明确表示已被消耗 // _gen = Generator{}; } Iterator begin() && { return Iterator(std::move(_gen)); } // ... 其他成员保持不变 ... };
这样修改后,左值iterable调用begin()后,内部的_gen已经被移走,再次调用begin()的话,迭代器里的Generator会处于已被消耗的状态(如果你的Generator是基于只读流等一次性资源的话,自然就无法再生成值;如果是示例里的简单计数逻辑,你可以给Generator加一个_isConsumed标记,让hasMore()返回false)。
本质上,range-based for循环的规则就是:对于左值范围对象,调用左值begin();对于右值范围对象,调用右值begin()。你之前的代码里用了左值iterable,所以编译器找不到对应的左值begin()才报错。
备注:内容来源于stack exchange,提问作者user3612643

