219

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?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Andriy
  • 8,486
  • 3
  • 27
  • 51

7 Answers7

250

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.

JFMR
  • 23,265
  • 4
  • 52
  • 76
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    I thought the new protocol was cbegin(vec) rather than vec.cbegin(). – Kaz Dragon Aug 17 '12 at 12:41
  • 21
    @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. – Nicol Bolas Aug 17 '12 at 13:10
  • 22
    Apparently, `std::cbegin/cend` will be added in C++14. See http://en.cppreference.com/w/cpp/iterator/begin – Adi Shavit Nov 06 '13 at 15:59
  • 14
    @NicolBolas is `for(auto &item : std::as_const(vec))` equivalent to `for(const auto &item : vec)`? – luizfls Oct 18 '17 at 20:34
  • 12
    @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 Sep 24 '18 at 21:00
  • There always has been `const_cast` for pre-C++17. These methods are completely unnecessary and they just pollute this already polluted language. – mip Dec 12 '22 at 00:12
  • @mip: "*There always has been const_cast for pre-C++17.*" Which requires doing this: `const_cast(variable)` rather than the much shorter `std::as_const(variable)`. I know which one I'd rather read. Also performing a `const_cast` on an iterator is 100% useless. – Nicol Bolas Dec 12 '22 at 01:08
  • I meant `cbegin` and `cend` are unnecessary methods and even without `std::as_const` you could enforce const with `const_cast`. You don't need "silly reference". First part of your answer is simply wrong. Why `const_cast` on iterator is 100% useless? – mip Dec 12 '22 at 01:22
  • @mip: "*Why `const_cast` on iterator is 100% useless?*" Because iterators don't propagate `const`-ness to their contents. That's why `iterator` and `const_iterator` are permitted to be different types. `templatename`, as far as the language is concerned, is a different type from `templatename`, even though they are generated by the same template. Therefore, to allow an iterator to permit only `const`-access, the `const_iterator` for a container will need to be a different type from `iterator`. – Nicol Bolas Dec 12 '22 at 02:18
  • Because iterators don't propagate constness it's not making `const_cast` 100% useless. Consider two overloaded methods `f(const iterator &)` and `f(iterator &)`. How to force the first one on non-const `iterator`? Yes, it's very specific example, but you are making very broad statement. Also the point was that you could use `const_cast` instead of `std::as_const` and that your satement "In C++03 ... there's no way to ensure that it does" is wrong. – mip Dec 13 '22 at 01:20
  • @mip: "*Yes, it's very specific example, but you are making very broad statement.*" I consider "100% useless" and "100% useless except for edge cases that serve no useful purpose" to be equivalent statements. "*that your satement "In C++03 ... there's no way to ensure that it does" is wrong.*" There was another sentence following that one, which explains that there are exceptions, and it even provides such an example. If you're just going to stop reading when you see a sentence you don't agree with, you're going to find yourself very disappointed. – Nicol Bolas Dec 13 '22 at 05:08
  • I could give you more practical examples, but the comment section is pretty narrow. If you claim that C++17 has the solution, then C++03 also has the solution, because `const_cast` is exactly that - a slightly more verbose version of `std::as_const`. Maybe I should mention C-style casts as well - they can be also used to add or remove `const`. – mip Dec 13 '22 at 06:27
69

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();
Community
  • 1
  • 1
Stefan Majewsky
  • 5,427
  • 2
  • 28
  • 51
  • 1
    Couldn't you do const auto const_iterator = container.begin()? – allyourcode Jan 08 '13 at 20:49
  • 2
    @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`. – aschepler Jan 08 '13 at 21:15
  • 2
    @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. – allyourcode Jan 11 '13 at 19:18
  • 28
    @allyourcode: That would give you an iterator which is constant, but that's very different from an iterator to constant data. – aschepler Jan 11 '13 at 19:56
  • 2
    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. – Columbo May 26 '15 at 10:27
  • 19
    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". ;) – Stefan Majewsky May 26 '15 at 11:57
  • 3
    @Columbo: Why write one, when there's now [`std::as_const`](http://en.cppreference.com/w/cpp/utility/as_const). So `std::as_const(vec).begin()` and `vec.cbegin()` are equivalent – Eric Jan 24 '17 at 15:37
  • @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 – Sourabh Choure Jun 03 '21 at 02:14
  • Even before `std::as_const` you could use `const_cast<>` on the right hand. They have polluted standard library with completely unnecessary methods and now we have ridiculous amount of functions that just return iterator to confuse young programmers.... – mip Dec 12 '22 at 00:10
15

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
8

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.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
6

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
chris g.
  • 162
  • 1
  • 4
2

iterator and const_iterator have inheritance relationship and an implicit conversion occurs when compared with or assigned to the other type.

class T {} MyT1, MyT2, MyT3;
std::vector<T> MyVector = {MyT1, MyT2, MyT3};
for (std::vector<T>::const_iterator it=MyVector.begin(); it!=MyVector.end(); ++it)
{
    // ...
}

Using cbegin() and cend() will increase performance in this case.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}
hkBattousai
  • 10,583
  • 18
  • 76
  • 124
  • 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 – underscore_d Sep 24 '18 at 21:15
  • @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](https://youtu.be/zBkNBP00wJE?t=1613) from a Jason Turner's talk for a live example of this. – brainplot Jan 27 '19 at 10:55
  • @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. – underscore_d Jan 27 '19 at 12:20
  • @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. – brainplot Jan 27 '19 at 14:32
0

its simple, cbegin returns a constant iterator where begin returns just an iterator

for better understanding lets take two scenarios here

scenario - 1 :

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.begin();i< v.end();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

this will run because here iterator i is not constant and can be incremented by 5

now let's use cbegin and cend denoting them as constant iterators scenario - 2 :

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.cbegin();i< v.cend();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

this is not going to work, because you cant update the value using cbegin and cend which returns the constant iterator