What is the reason behind cbegin/cend?

53,261

Solution 1

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.

Solution 2

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();

Solution 3

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.

Solution 4

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.

Solution 5

Just stumbled upon this question... I know it's alredy answerd and it's just a side node...

auto const it = container.begin() is a different type then auto it = container.cbegin()

the difference for int[5] (using pointer, which i know don't have the begin method but show nicely the difference... but would work in c++14 for std::cbegin() and std::cend(), which is essentially what one should use when it's here)...

int numbers = array[7];
const auto it = begin(numbers); // type is int* const -> pointer is const
auto it = cbegin(numbers);      // type is int const* -> value is const
Share:
53,261

Related videos on Youtube

Andrey
Author by

Andrey

Experienced C++ developer.

Updated on July 08, 2022

Comments

  • Andrey
    Andrey almost 2 years

    I wonder why cbegin and cend were introduced in C++11?

    What are cases when calling these methods makes a difference from const overloads of begin and end?

  • RobotMan
    RobotMan almost 12 years
    I thought the new protocol was cbegin(vec) rather than vec.cbegin().
  • Nicol Bolas
    Nicol Bolas almost 12 years
    @Kaz: There are no std::cbegin/cend free functions the way that std::begin/std::end exist. It was an oversight by the committee. If those functions did exist, that would generally be the way to use them.
  • allyourcode
    allyourcode over 11 years
    Couldn't you do const auto const_iterator = container.begin()?
  • aschepler
    aschepler over 11 years
    @allyourcode: Doesn't help. To the compiler, const_iterator is just another identifier. Neither version uses a lookup of the usual member typedefs decltype(container)::iterator or decltype(container)::const_iterator.
  • allyourcode
    allyourcode over 11 years
    @aschepler I don't understand your second sentence, but I think you missed the "const" in front of "auto" in my question. Whatever auto comes to, seems that const_iterator should be const.
  • aschepler
    aschepler over 11 years
    @allyourcode: That would give you an iterator which is constant, but that's very different from an iterator to constant data.
  • Adi Shavit
    Adi Shavit over 10 years
    Apparently, std::cbegin/cend will be added in C++14. See en.cppreference.com/w/cpp/iterator/begin
  • Columbo
    Columbo almost 9 years
    There is a simple way of ensuring that you get a const_iterator with auto: Write an auxiliary function template called make_const to qualify the object argument.
  • Stefan Majewsky
    Stefan Majewsky almost 9 years
    Maybe I'm just not in the C++ mindset anymore, but I can't see a connection between the concepts of "a simple way" and "write an auxiliary function template". ;)
  • Eric
    Eric over 7 years
    @Columbo: Why write one, when there's now std::as_const. So std::as_const(vec).begin() and vec.cbegin() are equivalent
  • luizfls
    luizfls over 6 years
    @NicolBolas is for(auto &item : std::as_const(vec)) equivalent to for(const auto &item : vec)?
  • underscore_d
    underscore_d over 5 years
    @luizfls Yes. Your code says the item will not be modified by putting the const on the reference. Nicol's views the container as const, so auto deduces a const reference. IMO auto const& item is easier and clearer. It's unclear why std::as_const() is good here; I can see it'd be useful when passing something non-const to generic code where we can't control the type that gets used, but with range-for, we can, so it just seems like added noise to me there.
  • underscore_d
    underscore_d over 5 years
    It took me a while to realise you meant performance is saved by avoiding conversion when initialising & comparing iterators, not the popular myth that const's main benefit is performance (which it isn't: it's semantically correct and safe code). But, while you have a point, (A) auto makes that a non-issue; (B) in talking about performance, you missed a main thing you should've done here: cache the end iterator by declaring a copy of it in the init-condition of the for loop, & compare to that, instead of getting a new copy by value for every iteration. That'll make your point better. :P
  • brainplot
    brainplot over 5 years
    @underscore_d const can definitely help achieve better performance, not because of some magic in the const keyword itself but because the compiler can enable some optimizations if it knows that the data won't be modified, which would not be possible otherwise. Check this bit from a Jason Turner's talk for a live example of this.
  • underscore_d
    underscore_d over 5 years
    @brainplot I didn't say it couldn't. I said that's not its main benefit and that I think it gets overstated, when the real benefit of it is semantically correct and safe code.
  • brainplot
    brainplot over 5 years
    @underscore_d Yes, I agree on that. I was just making it explicit that const can (almost indirectly) lead to performance benefits; just in case somebody reading this may think "I won't bother adding const if the generated code isn't affected in any way ever", which is not true.
  • Sourabh Choure
    Sourabh Choure almost 3 years
    @allyourcode When you use const auto const_iterator = container.begin() it will create a top-level constant and not a low-level constant, as clearly mentioned by @aschepler