std::begin(X)与X.begin()处理右值的差异:是标准疏漏还是使用错误?
std::begin与容器成员begin()处理右值的行为差异
普遍认知里std::begin(c)和c.begin()是等价的,但实际处理右值容器时,两者的行为存在反直觉的差异:
- 调用容器的
begin()&&(右值成员版本)会返回普通迭代器(iterator) - 调用
std::begin处理右值容器时,会匹配到const C&版本的重载,返回常量迭代器(const_iterator)
这并非编译器(GCC/Clang)的疏漏,而是C标准的规定:std::begin并未提供针对右值引用的重载,右值容器会被绑定到const C&参数上,因此触发常量版本的迭代器返回;而容器自身的begin()&&是C17起新增的成员函数,专门用于右值对象,允许调用者修改右值容器内的元素(毕竟右值的生命周期局限于当前表达式,修改操作是合法的)。
验证代码
#include <unordered_map> #include <type_traits> using MyMap = std::unordered_map<int, int>; using Iter = typename MyMap::iterator; using ConstIter = typename MyMap::const_iterator; // 右值容器调用成员begin(),返回普通迭代器,断言通过 static_assert(std::is_same_v<decltype(MyMap().begin()), Iter>); // std::begin处理右值,匹配const&重载返回const_iterator,断言失败 static_assert(std::is_same_v<decltype(std::begin(MyMap())), Iter>); // 右值容器的成员begin()不返回const_iterator,断言失败 static_assert(std::is_same_v<decltype(MyMap().begin()), ConstIter>); // std::begin处理右值返回const_iterator,断言通过 static_assert(std::is_same_v<decltype(std::begin(MyMap())), ConstIter>);
临时解决方案(Workaround)
通过自定义模板优先调用容器自身的begin()成员,完美转发参数以匹配右值版本:
template<class T> concept has_begin = requires (T t) { t.begin(); }; template<class T> decltype(auto) my_begin(T&& x) { if constexpr(has_begin<std::remove_reference_t<T>>) return std::forward<T>(x).begin(); else return std::begin(x); }
受影响的示例代码
以下代码在传入非const右值时会失效:
template<class T> decltype(auto) do_things(T&& in) { const auto _end = in.end(); auto it = std::begin(std::forward<T>(in)); while(it != _end) { it->second += it->first; // 无法修改:std::begin返回const_iterator,指向的元素是const的 ++it; } return std::forward<T>(in); }
注:部分自定义类的begin()&&与begin()&行为差异明显,必须通过右值传递才能触发预期逻辑。
内容的提问来源于stack exchange,提问作者igel
相关产品推荐
相关产品推荐

