【发布时间】:2015-01-13 14:42:48
【问题描述】:
最近我试图修复一个非常困难的 const 正确性编译器错误。它最初表现为 Boost.Python 深处的多段模板呕吐错误。
但这无关紧要:这一切都归结为以下事实:C++11 std::begin 和 std::end 迭代器函数没有重载以获取 R 值。
std::begin 的定义是:
template< class C >
auto begin( C& c ) -> decltype(c.begin());
template< class C >
auto begin( const C& c ) -> decltype(c.begin());
因此,由于没有 R-value/Universal Reference 重载,如果您将 R-value 传递给它,您将得到一个 const 迭代器。
那我为什么要关心?好吧,如果你曾经有过某种“范围”容器类型,例如“视图”、“代理”或“切片”,或者某种呈现另一个容器的子迭代器范围的容器类型,通常很方便使用 R 值语义并从临时切片/范围对象中获取非常量迭代器。但是使用std::begin,你就不走运了,因为std::begin 总是会返回一个用于R 值的常量迭代器。这是 C++03 程序员在 C++11 为我们提供 R 值之前经常感到沮丧的一个老问题 - 即临时变量总是绑定为 const 的问题。
那么,为什么std::begin 不被定义为:
template <class C>
auto begin(C&& c) -> decltype(c.begin());
这样,如果c 是常量,我们会得到C::const_iterator,否则会得到C::iterator。
起初,我以为是为了安全。如果你向std::begin 传递了一个临时值,像这样:
auto it = std::begin(std::string("temporary string")); // never do this
...你会得到一个无效的迭代器。但后来我意识到这个问题在当前的实现中仍然存在。上面的代码只会返回一个无效的 const-iterator,它可能会在取消引用时出现段错误。
那么,为什么std::begin没有定义为采用 R 值(或更准确地说,Universal Reference)?为什么有两个重载(一个用于const,一个用于non-const)?
【问题讨论】:
-
你忘记了
std::forward<C>(c)。 -
不确定为什么在这种情况下这很重要 - 在这种情况下,重要的是
c是否为const,C&&降级后不会影响的问题C& -
容器可能会使用 ref 限定符重载
begin,从而使返回的迭代器类型取决于对象参数的值类别。但是,是的,出于演示目的无关紧要。 -
@Columbo,没错 - 好点。
-
显然它们不再被称为通用引用,而是forwarding references。