以下代码未能通过静态断言:
#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
答案 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
根本不是容器。 cbegin
和cend
设计用于容器。有一些例外情况(数组,initializer_list
)需要特别注意,但是显然标准库不能提及类似gsl::span
的东西。
第二,是LWG 2128引入了全局cbegin
和cend
。让我们看看相关部分怎么说:
通过调用
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
提供特殊维护的工作。