Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::cbegin() not call .cbegin() on the container?

Tags:

c++

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

like image 942
TripShock Avatar asked Jun 30 '19 23:06

TripShock


2 Answers

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.

like image 155
eerorika Avatar answered Nov 01 '22 03:11

eerorika


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 calling std::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 lacks cbegin/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.

like image 22
L. F. Avatar answered Nov 01 '22 05:11

L. F.