The following code fails the static assertion:
#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)>);
}
This fails because std::cbegin()
calls the .begin()
method on a const ref of the container. For standard-defined containers, this returns a const_iterator
, which is the same type that .cbegin()
returns. However, gsl::span
is a bit unique because it models a sort of "borrow type". A const gsl::span
behaves like a const pointer; the span itself is const, but what it points-to is not const. Hence, the .begin()
method on a const gsl::span
still returns a non-const iterator, whereas explicitly calling .cbegin()
returns a const iterator.
I'm curious as to why std::cbegin()
was not defined as invoking .cbegin()
on the container (which all standard containers seem to implement) to account for cases such as this.
This is somewhat related to: Why does std::cbegin return the same type as std::begin
this fails because
std::cbegin()
calls the.begin()
To be more precise, std::cbegin
calls std::begin
, which in the generic overload calls c.begin
.
For what it's worth, it should be possible to fix gsl::span
to return const iterator upon std::cbegin
if the designers of gsl specify that there is a specialisation for the generic overload of std::cbegin
for gsl::span
that uses c.cbegin
instead of std::begin
, if that is the desired behaviour. I don't know their reasoning for not specifying such specialisation.
As for reasoning for why std::cbegin
uses std::begin
, I do not know for fact either, but it does have the advantage of being able to support containers that have a c.begin
member, but not a c.cbegin
member, which can be seen as a less strict requirement, as it can be satisfied by custom containers written prior to C++11, when there was no convention of providing a c.cbegin
member function.
First, note that, per [tab:container.req]:
Expression:
a.cbegin()
Return type:
const_iterator
Operational semantics:
const_cast<X const&>(a).begin();
Complexity: constant
Therefore, gsl::span
is not a container at all. cbegin
and cend
are designed to work with containers. There are some exceptions (arrays, initializer_list
) that require special care, but apparently the standard library cannot mention something like gsl::span
.
Second, it is LWG 2128 that introduced global cbegin
and cend
. Let's see what the relevant part says:
Implement
std::cbegin/cend()
by callingstd::begin/end()
. This has numerous advantages:
It automatically works with arrays, which is the whole point of these non-member functions.
It works with C++98/03-era user containers, written before
cbegin/cend()
members were invented.It works with
initializer_list
, which is extremely minimal and lackscbegin/cend()
members.[container.requirements.general] guarantees that this is equivalent to calling
cbegin/cend()
members.
Essentially, calling std::begin/end()
save the work of providing special care for arrays and initializer_list
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With