为何基于范围的for循环无法匹配右值引用修饰的begin/end成员函数?
嘿,这个问题我之前踩过同款坑!咱们来一步步拆解为啥你的代码会编译报错~
首先得搞懂基于范围的for循环背后偷偷做了啥。当你写for (int x : MyContainer(container))时,编译器其实会帮你生成类似下面的代码:
auto&& __range = MyContainer(container); // 把临时对象绑定到转发引用 auto __begin = __range.begin(); // 尝试调用begin() auto __end = __range.end(); // 尝试调用end() for (; __begin != __end; ++__begin) { int x = *__begin; std::cout << x << " "; }
重点来了:__range是一个有名字的变量,不管它的类型是不是右值引用,它本身都是左值!C++里所有有名字的变量都是左值——就像你给一个临时快递包贴了个名字标签,它就不再是无人认领的临时包裹,而是归你所有的“左值”了。
而你给begin()和end()加了&&限定,这个限定的意思是:“只有当调用我的对象是无名字的右值(比如临时对象)或者被std::move转成的右值时,才能调用我”。但现在__range是有名字的左值,编译器找不到对应的左值版本的begin()/end(),自然就报编译错误了。
那怎么解决这个问题呢?给你几个实用的方案:
方案一:补充左值版本的
begin()/end()
如果你希望左值和右值对象都能用来做范围for,那直接给类加上不带引用限定的成员函数就行:auto begin() & { return data.begin(); } auto end() & { return data.end(); } auto begin() && { return data.begin(); } auto end() && { return data.end(); }这样不管是左值对象还是右值临时对象,范围for都能找到对应的begin/end。
方案二:实现非成员的
begin()/end()重载
如果你只想让右值对象能用范围for,又不想加左值版本的成员函数,可以写针对右值的非成员函数(利用ADL规则,编译器会自动找到它们):auto begin(MyContainer&& c) { return c.data.begin(); } auto end(MyContainer&& c) { return c.data.end(); }这样当范围for找不到成员版本的begin/end时,就会匹配到这个非成员的右值重载。
方案三:手动展开范围for的逻辑
如果你就想硬用右值限定的成员函数,那可以跳过范围for,自己写底层循环:auto tmp = MyContainer(container); for (auto it = std::move(tmp).begin(); it != std::move(tmp).end(); ++it) { std::cout << *it << " "; }这里
std::move(tmp)把左值的tmp转成了右值,就能调用到你定义的右值限定版begin/end了。
内容来源于stack exchange

