Recently I was trying to fix a pretty difficult const-correctness compiler error. It initially manifested as a multi-paragraph template vomit error deep within Boost.Python.
But that's irrelevant: it all boiled down to the following fact: the C++11 std::begin
and std::end
iterator functions are not overloaded to take R-values.
The definition(s) of std::begin
are:
template< class C >
auto begin( C& c ) -> decltype(c.begin());
template< class C >
auto begin( const C& c ) -> decltype(c.begin());
So since there is no R-value/Universal Reference overload, if you pass it an R-value you get a const iterator.
So why do I care? Well, if you ever have some kind of "range" container type, i.e. like a "view", "proxy" or a "slice" or some container type that presents a sub iterator range of another container, it is often very convenient to use R-value semantics and get non-const iterators from temporary slice/range objects. But with std::begin
, you're out of luck because std::begin
will always return a const-iterator for R-values. This is an old problem which C++03 programmers were often frustrated with back in the day before C++11 gave us R-values - i.e. the problem of temporaries always binding as const
.
So, why isn't std::begin
defined as:
template <class C>
auto begin(C&& c) -> decltype(c.begin());
This way, if c
is constant we get a C::const_iterator
and a C::iterator
otherwise.
At first, I thought the reason was for safety. If you passed a temporary to std::begin
, like so:
auto it = std::begin(std::string("temporary string")); // never do this
...you'd get an invalid iterator. But then I realized this problem still exists with the current implementation. The above code would simply return an invalid const-iterator, which would probably segfault when dereferenced.
So, why is std::begin
not defined to take an R-value (or more accurately, a Universal Reference)? Why have two overloads (one for const
and one for non-const
)?