Isn't this const auto& redundant?

I found this code in one of Stroustrup's books:

void print_book(const vector<Entry>& book)
{
     for (const auto& x : book) // for "auto" see §1.5
           cout << x << '\n';
}

But the const seems redundant because x will be deduced to be a const_iterator since book is const in the parameter. Is const auto really better?


In my opinion, it looks explicit which is why it is better. So if I mean const, then I'd prefer to write it explicitly. It enhances local reasoning and thus helps others to understand the code comparatively easily — other programmers dont have to look at the declaration of book and infer that x is const as well.


Code isn't just for the compiler to read — it's also for humans to read. Conciseness is only good when it does not come at the expense of readability.

In the given code, deducing constness is immediate. With your proposed change, deducing constness would require the reader to accurately remember or review the definition of book.

If the reader is intended to be aware of constness, brevity would be counterproductive here.


It's much clearer seeing the keyword const. You immediately recognize that it's not going to be modified, if the const keyword wasn't there we wouldn't know unless we looked up to the function signature.

Using the const keyword explicitly is much better for the readers of the code.


Moreover, when you read the code below you expect that x is modifiable

for (auto& x : book) { ... }

And then when you try to modify x you'll find that it results in an error because book is const.
This is not clear code in my opinion. It's better to state in code that x is not going to be modified so that others that read the code will have their expectations right.


I'm going to take a different angle here. const and const_iterator mean completely different things.

const applied to an iterator means you cannot modify the iterator itself, but you can modify the element it points to, much like a pointer declared like int* const. const_iterator means you cannot modify the element it points to, but you can still modify the iterator itself (ie. you can increment and decrement it) much like a pointer declared like const int*.

std::vector<int> container = {1, 2, 3, 4, 5};
const std::vector<int> const_container = {6, 7, 8, 9, 0};
auto it1 = container.begin();
const auto it2 = container.begin();
auto it3 = const_container.begin();
const auto it4 = const_container.begin();

*it1 = 10; //legal
*it2 = 11; //legal
*it3 = 12; //illegal, won't compile
*it4 = 13; //illegal, won't compile

it1++; //legal
it2++; //illegal, won't compile
it3++; //legal
it4++; //illegal, won't compile

As you can see, the ability to modify the element the iterator points to depends only on whether it's an iterator or const_iterator, and the ability to modify the iterator itself depends only on whether the variable declaration for the iterator has a const qualifier.

Edit: I just realized this is a range for and therefore there is no iterators at play here at all (at least not explicitly). x is not an iterator and is actually a direct reference to an element of the container. But you have the right idea, it will be deduced to have the const qualifier regardless of whether one is explicitly written.