为什么std :: cbegin()不对容器调用.cbegin()?

时间:2019-06-30 23:20:29

标签: c++

以下代码未能通过静态断言:

#include <gsl/span>
#include <iterator>
#include <type_traits>

int main()
{
    int theArr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    gsl::span<int> theSpan{ theArr, std::size(theArr) };

    using std::cbegin;
    auto it1 = cbegin(theSpan);
    auto it2 = theSpan.cbegin();
    static_assert(std::is_same_v<decltype(it1), decltype(it2)>);
}

此操作失败,因为std::cbegin()在容器的const ref上调用了.begin()方法。对于标准定义的容器,这将返回const_iterator,与.cbegin()返回的类型相同。但是,gsl::span有点独特,因为它模拟了一种“借阅类型”。 const gsl::span的行为类似于const指针; span本身是const,但指向的不是const。因此,.begin()上的const gsl::span方法仍返回非常量迭代器,而显式调用.cbegin()则返回常量迭代器。

我很好奇为什么未将std::cbegin()定义为在容器(所有标准容器似乎都在实现)上调用.cbegin()来解决此类情况。

这与以下内容有关:Why does std::cbegin return the same type as std::begin

2 个答案:

答案 0 :(得分:12)

  

此操作失败,因为std::cbegin()调用了.begin()

更准确地说,std::cbegin调用std::begin,而在一般重载中调用c.begin

对于它的价值,如果gsl的设计人员指定gsl::span的泛型重载是特殊的,则应该有可能修复std::cbegin以在std::cbegin上返回const迭代器。对于使用gsl::span而不是c.cbegin的{​​{1}},如果这是期望的行为。我不知道他们不指定这种专业化的理由。

关于std::begin为什么使用std::cbegin的原因,我也不知道,但是它的确具有能够支持具有std::begin成员的容器的优点,但不是c.begin成员,这可以视为不太严格的要求,因为在没有约定提供c.cbegin成员的情况下,可以由C ++ 11之前编写的自定义容器来满足功能。

答案 1 :(得分:8)

首先,请注意,根据[tab:container.req]

  

表达式:a.cbegin()

     

返回类型:const_­iterator

     

操作语义:const_­cast<​X const&​>(a)​.begin();

     

复杂度:恒定

因此,gsl::span根本不是容器。 cbegincend设计用于容器。有一些例外情况(数组,initializer_list)需要特别注意,但是显然标准库不能提及类似gsl::span的东西。

第二,是LWG 2128引入了全局cbegincend。让我们看看相关部分怎么说:

  

通过调用std::cbegin/cend()实现std::begin/end()。这有   众多优点:

     
      
  • 它自动处理数组,这是这些非成员函数的重点。

  •   
  • 它与C cbegin/cend()成员发明之前编写的C ++ 98/03时代的用户容器兼容。

  •   
  • 它与initializer_list一起使用,这极少且缺少cbegin/cend()成员。

  •   
  • [container.requirements.general]   保证这等同于调用cbegin/cend()成员。

  •   

本质上,调用std::begin/end()可以节省为数组和initializer_list提供特殊维护的工作。