Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Given cbegin(), cend(), why is there no cfront(), cback(), cfind(), ...?

Tags:

c++

stl

c++14

So, in order to allow code such as

auto vect = ...;
auto it = vect.begin(), end = vect.end(); // want const_iterator, getting iterator

to pick the right overload of begin() and end(), even for non-const containers, the more explicit cbegin()/cend() functions were added.

Why stop there?

Associative containers have a find() method with the same problem. Sequence containers have front() and back(), again with the same problem.

Are these missing explicit const versions omissions, or by design?

like image 617
Marc Mutz - mmutz Avatar asked May 26 '15 10:05

Marc Mutz - mmutz


2 Answers

A wider API has cost, even just to skip over it when looking for the function you want.

template<class T>
T const as_const(T&& t) noexcept(noexcept(T(std::declval<T>())) {
  return std::forward<T>(t);
}
template<class T>
T const& as_const(T& t) noexcept {
  return t;
}

does most of what you want. It would even make cbegin obsolete.

(modifications done to the code above based off n4380 supplied by @T.C below. Code differs, because I think n4380 got it slightly wrong in the T&& case.)

like image 50
Yakk - Adam Nevraumont Avatar answered Sep 28 '22 21:09

Yakk - Adam Nevraumont


The purpose of cbegin/cend is to solve a specific problem. Consider this code:

std::vector<int> & v = //... v is a non-const reference

// For clarity, I want this iterator to be a const_iterator.
// This works because iterator is implicitly convertible to const_iterator
std::vector<int>::const_iterator iter = find(v.begin(),v.end(),42);

// (1) Now I want to use iter in an algorithm
std::find(iter, v.end(), 38); //Error, can not deduce the template parameter for Iter. 

// (2) This works
std::find(iter, const_cast<const std::vector<int> &>(v).end(), 38);

// (3) This is the same as (2).
std::find(iter, v.cend(), 38);

The problem is that, due to how template deduction rules work, the compiler can not deduce the template iterator argument in the statement (1), because Container::iterator and Container::const_iterator are (potentially) two completely unrelated types (even if the former is implicitly convertible in the latter).

The statement (2) is not exactly a beautiful line, that is why we need cend().

Now, front(), back() et similia all return a reference. A non-const reference can always be deduced as const in a templated function, that is:

template<class T> void f( const T & l, const T & r);

int main()
{
    int x; vector<int> v;

    //This will works even if the return type of front() is int&.
    f(x, v.front());
 }

Since Container::const_reference is required by the standard to be equal to const Container::value_type &, cfront()/cback() do not buy us anything.


It is worth mentioning that other containers library (looking at you Qt) are implemented using Copy-On-Write.

This means that calling a const function on such container is potentially much less expensive than calling the equivalent non-const version, simply because the non-const might copy the entire container under the hood.

For this reason the Qt containers have a lot of constFunction in their interface, and the user has the freedom to pick the right one.

like image 35
sbabbi Avatar answered Sep 28 '22 19:09

sbabbi