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_iteratorOperational 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