Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the reason behind cbegin/cend?

It's quite simple. Say I have a vector:

std::vector<int> vec;

I fill it with some data. Then I want to get some iterators to it. Maybe pass them around. Maybe to std::for_each:

std::for_each(vec.begin(), vec.end(), SomeFunctor());

In C++03, SomeFunctor was free to be able to modify the parameter it gets. Sure, SomeFunctor could take its parameter by value or by const&, but there's no way to ensure that it does. Not without doing something silly like this:

const std::vector<int> &vec_ref = vec;
std::for_each(vec_ref.begin(), vec_ref.end(), SomeFunctor());

Now, we introduce cbegin/cend:

std::for_each(vec.cbegin(), vec.cend(), SomeFunctor());

Now, we have syntactic assurances that SomeFunctor cannot modify the elements of the vector (without a const-cast, of course). We explicitly get const_iterators, and therefore SomeFunctor::operator() will be called with const int &. If it takes it's parameters as int &, C++ will issue a compiler error.


C++17 has a more elegant solution to this problem: std::as_const. Well, at least it's elegant when using range-based for:

for(auto &item : std::as_const(vec))

This simply returns a const& to the object it is provided.


Beyond what Nicol Bolas said in his answer, consider the new auto keyword:

auto iterator = container.begin();

With auto, there's no way to make sure that begin() returns a constant operator for a non-constant container reference. So now you do:

auto const_iterator = container.cbegin();

Take this as a practical usecase

void SomeClass::f(const vector<int>& a) {
  auto it = someNonConstMemberVector.begin();
  ...
  it = a.begin();
  ...
}

The assignment fails because it is a nonconst iterator. If you used cbegin initially, the iterator would have had the right type.


From http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1674.pdf:

so that a programmer can directly obtain a const_iterator from even a non-const container

They gave this example

vector<MyType> v;

// fill v ...
typedef vector<MyType>::iterator iter;
for( iter it = v.begin(); it != v.end(); ++it ) {
    // use *it ...
}

However, when a container traversal is intended for inspection only, it is a generally preferred practice to use a const_iterator in order to permit the compiler to diagnose const-correctness violations

Note that the working paper also mentions adapter templates, that now have been finalized as std::begin() and std::end() and that also work with native arrays. The corresponding std::cbegin() and std::cend() are curiously missing as of this time, but they might also be added.