在 C++11 标准中是否指定 std::begin(Container&&) 返回const_iterator?

Is it specified in the C++11 standard that std::begin(Container&&) returns const_iterator?

本文关键字:Container 返回 const iterator begin 标准 C++11 是否 std      更新时间:2023-10-16

这里有一个相关代码的链接:

#include <iostream>
#include <string>
#include <vector>
#include <type_traits>
int main()
{
  std::vector<int> v{1, 2, 3, 4, 5};
  auto iter = begin(std::move(v));
  if(std::is_const<typename std::remove_reference<decltype(*iter)>::type>::value)
    std::cout<<"is constn";
  return 0;
}

http://coliru.stacked-crooked.com/a/253c6373befe8e50

我遇到这种行为是因为decltype表达式中的declval<Container>()std::begin.gcc 和 clang 都返回迭代器,这些迭代器在取消引用时会产生常量引用。这可能是有意义的,因为 r 值引用通常绑定到您不想改变的过期对象。但是,我找不到任何有关此的文档来确定它是否是标准规定的。我找不到任何相关的begin()重载或 ref 合格的重载Container::begin().

更新:答案阐明了正在发生的事情,但交互可能很微妙,如下所示:

#include <iostream>
#include <string>
#include <vector>
#include <type_traits>
int main()
{
  if(std::is_const<typename std::remove_reference<decltype(*begin(std::declval<std::vector<std::string>>()))>::type>::value)
    std::cout<<"(a) is constn";
  if(!std::is_const<typename std::remove_reference<decltype(*std::declval<std::vector<std::string>>().begin())>::type>::value)
    std::cout<<"(b) is not constn";
  if(!std::is_const<typename std::remove_reference<decltype(*begin(std::declval<std::vector<std::string>&>()))>::type>::value)
    std::cout<<"(c) is not constn";
  return 0;
}

http://coliru.stacked-crooked.com/a/15c17b288f8d69bd

天真地,当 ::begin 只是根据调用 vector::start 来定义时,你不会期望 (a( 和 (b( 有不同的结果。但是,缺少接受非常量 r 值引用并返回迭代器的 std::start 重载(或返回 const_iterator 的 ref 限定的 vector::begin 重载(会导致这种情况发生。

正如您在 http://en.cppreference.com/w/cpp/iterator/begin 中看到的,有趣的重载是:

template<class C> auto begin(C& c) -> decltype(c.begin());
template<class C> auto begin(const C& c) -> decltype(c.begin());

并且std::vector<int>&&只能绑定到第二个重载(因此返回const_iterator(。

让我们尝试逐步分析会发生什么:

  1. 您正在调用 std::begin(std::vector<int>&&) ,但std::begin没有采用右值的重载:

    template< class C > 
    auto begin( C& c ) -> decltype(c.begin());
    template< class C > 
    auto begin( const C& c ) -> decltype(c.begin());
    

  1. 由于引用折叠,临时 (xvalue( 将仅绑定到const左值引用:

    如果你用xvalue调用Fwd,我们再次得到Type&&作为v的类型。这将不允许您调用采用非常量左值的函数,因为 xvalue 无法绑定到非常量左值引用。它可以绑定到一个 const lvalue 引用,所以如果 Call 使用 const&,我们可以用 xvalue 调用 Fwd。

    (来自链接的答案(。


  1. 因此,

     template<class C> auto begin(const C& c) -> decltype(c.begin());
    

    正在调用重载,这将返回一个const迭代器。

    为什么?

    因为std::begin(v)调用v.begin() ,所以在std::vectorconst实例上调用时会返回一个const_iterator