【问题标题】:std::begin and R-valuesstd::begin 和 R 值
【发布时间】:2015-01-13 14:42:48
【问题描述】:

最近我试图修复一个非常困难的 const 正确性编译器错误。它最初表现为 Boost.Python 深处的多段模板呕吐错误。

但这无关紧要:这一切都归结为以下事实:C++11 std::beginstd::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&lt;C&gt;(c)
  • 不确定为什么在这种情况下这很重要 - 在这种情况下,重要的是 c 是否为 constC&amp;&amp; 降级后不会影响的问题C&amp;
  • 容器可能会使用 ref 限定符重载 begin,从而使返回的迭代器类型取决于对象参数的值类别。但是,是的,出于演示目的无关紧要。
  • @Columbo,没错 - 好点。
  • 显然它们不再被称为通用引用,而是forwarding references

标签: c++ c++11 iterator


【解决方案1】:

上面的代码只会返回一个无效的常量迭代器

不完全是。迭代器将一直有效,直到迭代器引用的临时对象在词法上创建的完整表达式结束。所以像

std::copy_n( std::begin(std::string("Hallo")), 2,
             std::ostreambuf_iterator<char>(std::cout) );

仍然是有效的代码。当然,在您的示例中,it 在语句末尾无效。

修改临时值或 xvalue 有什么意义?这可能是范围访问器的设计者在提出声明时考虑的问题之一。他们没有考虑.begin().end() 返回的迭代器在其生命周期后仍然有效的“代理”范围;也许正是因为在模板代码中,它们无法与正常范围区分开来——我们当然不想修改临时的非代理范围,因为那是没有意义的并且可能会导致混淆。

但是,您首先不需要使用std::begin,而是可以使用 using-declaration 来声明它们:

using std::begin;
using std::end;

并使用 ADL。这样,您就可以为 Boost.Python (o.s.) 使用的类型声明命名空间范围 beginend 重载,并规避 std::begin 的限制。例如

iterator begin(boost_slice&& s) { return s.begin(); }
iterator end  (boost_slice&& s) { return s.end()  ; }

// […]

begin(some_slice) // Calls the global overload, returns non-const iterator

为什么有两个重载(一个用于 const,一个用于非 const)?

因为我们仍然希望支持右值对象(它们不能被T&amp; 形式的函数参数获取)。

【讨论】:

    猜你喜欢
    • 2020-09-22
    • 2016-09-12
    • 2014-12-05
    • 2012-12-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多