19

I've recently been learning C++ and just today have been introduced to const and the concept of const correctness. In an attempt to better understand the theory, I've been writing a series of simple programs to make sure that I understand the concept correctly. I thought I understood everything, but then when using the auto keyword in one of the programs, I seem to have got a bit stuck.

In order to test that I understood how const pointers work I wrote a simple program. I won't bother posting the whole thing since there's only two parts of it that are relevant. I have a class with a const data member of type int:

const int tryToChangeMe;

Within this class I also have a member function that returns a const pointer to the above const int:

const int* const MyClass::test()
{
    return &tryToChangeMe;
}

In my main function I then call the above function, making use of the auto keyword. In order to test that what I think I know about const is correct I then attempt to reassign the tryToChangeMe variable through the pointer. Like so:

auto temp = myClass.test();
*temp = 100;

As I expected, the program wouldn't compile due to the error that I caused when trying to assign a value to a const variable. However, I didn't just return a pointer to const, I returned a const pointer to a const (at least that's what I thought I did). So to test this, I attempted to reassign the pointer to a new memory address, quite confident that I'd get a similar compilation error:

temp = new int;

But quite confusingly the program compiled without any issue. Stepping through with the debugger revealed that sure enough, the pointer was losing its original address and being assigned a brand new one. Wondering what was going on, I just happened by chance to remove the auto keyword and replace it with the variable's full type:

const int* const temp = myClass.test();

Upon testing everything again, the results were as expected and this time I was not able to reassign the pointer to a new address.

So after all that I guess my question is, why? Why does the auto keyword allow you to bypass the const qualifier of pointers? Did I do something wrong?

By the way, I'm not sure if it matters but I'm using Visual Studio 2015 preview

Praetorian
  • 106,671
  • 19
  • 240
  • 328
Pete
  • 193
  • 1
  • 4
  • This article by Herb Sutter might help you understand: http://herbsutter.com/2013/06/07/gotw-92-solution-auto-variables-part-1/ – Tas May 14 '15 at 00:25
  • Thank you. Now that I know about top-level const, things are becoming more clear. – Pete May 14 '15 at 00:57

4 Answers4

9

As already mentioned, auto ignores top level cv-qualifiers. Read this article to learn the details of how auto and decltype work.

Now, even if auto didn't ignore the const, in your case, temp would still not be const because top level cv-qualifiers on return types are ignored if the type being returned is of non-class type.

g++ even produces the following warning with -Wextra

warning: type qualifiers ignored on function return type [-Wignored-qualifiers]

This can be demonstrated by using C++14's decltype(auto). Unlike auto, decltype(auto) doesn't discard references and top level cv-qualifiers. If you modify your example by adding the following lines the code will still compile, proving that temp is not a const pointer.

decltype(auto) temp = myClass.test();
static_assert(std::is_same<const int*, decltype(temp)>{}, "");

On the other hand, if test() returns an object of class type with a top level cv-qualifier, then auto would still discard const, but decltype(auto) wouldn't.

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328
5

The reason is that auto variables are not by default const. The fact that you return const value does not mean it has to be assigned to a const variable; the value is copied after all (eventhough the value is a pointer). You can try it easily with explicit type specification as well. The value stored in myClass won't be changed when changing the variable temp and the pointer target is still const so constness is still honored.

StenSoft
  • 9,369
  • 25
  • 30
  • So, if I understand correctly, does that mean that returning a const pointer is essentially pointless? (no pun intended) – Pete May 14 '15 at 00:55
  • 1
    @Pete if you want a `const` pointer, then declare it with `const auto` – vsoftco May 14 '15 at 00:55
  • @Pete See [here](https://stackoverflow.com/questions/6299967/) and [here](http://stackoverflow.com/a/5689384/4538344) – StenSoft May 14 '15 at 01:00
  • @vsoftco Yeah I understand that now. But if the constness is still honoured without the const auto, then there's no real need. Correct? – Pete May 14 '15 at 01:01
  • @Pete the issue is that `const`-ness is not honoured by `auto`, it is discarded when doing `auto temp`. The cv-qualifiers **are not discarded** whenever you do `auto& temp`. In this latter case, the compiler does pattern matching. – vsoftco May 14 '15 at 01:08
  • @vsoftco Copying a `const` value does not violate constness. I think you meant that constness is not *retained*. – StenSoft May 14 '15 at 01:12
  • @StenSoft yes, sorry for the sloppy language. What I meant was that the `const`-ness of the pointer is not honoured. The data it points to remains `const` indeed. A typedef will help the most here. – vsoftco May 14 '15 at 01:13
  • @vsoftco The constness is still honored, no `const` value can be changed (not event the one returned from the function; its copy stored in `temp` is no longer `const`). Honoring means the program does not break the rules of [const correctness](https://en.wikipedia.org/wiki/Const_%28computer_programming%29#Loopholes_to_const-correctness), retaining means the qualifiers are kept the same. – StenSoft May 14 '15 at 13:27
4

When you write

auto temp = rhs;

type deduction works as follows:

  • if rhs is a reference, then the reference is ignored

  • the top-level cv(const-volatile)-qualifiers of rhs are also ignored (they are not ignored however if you do auto& temp = rhs;; in this case the compiler pattern-matches the type)

In your case, the type of the right hand side is

const int* const
           ^^^^^
           top-level cv qualifier

i.e. const pointer to const-int. The pointer is like any other variable, so it's const-ness will be discarded (technically, the top-level cv qualifier is const and it is discarded), hence you end up with the type of temp being deduced as

const int*

i.e. a non-const pointer to const-int, so it can be re-assigned. If you want to enforce the const-ness, then you have to declare the left hand side as

const auto temp = myClass.test();
^^^^^
need this

Scott Meyers has an excellent introduction to the subject (also available on his Effective Modern C++ book, Items 1 and 2 free to browse here), in which he explains how template type deduction works. Once you understand that, understanding auto is a breeze, since really auto type deduction mimic very closely the template type deduction system (with the notable exception of std::initializer_list<>).

EDIT

There is an additional rule for

auto&& temp = rhs;

but to understand it you need to understand how forwarding (universal) references work and how reference collapsing works.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
3

I'll provide some formal explanation from the Standard for that fact for standard-reference-searchers.:

The section N4296::7.1.6.4/7 [dcl.spec.auto]

If the placeholder is the auto type-specifier, the deduced type is determined using the rules for template argument deduction.

Now, the template argument dedcution N4296::14.8.2/3 [temp.deduct]:

[...] the function parameter type adjustments described in 8.3.5 are performed.

And finally N4296::8.3.5/5 [dcl.fct]

After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively. After producing the list of parameter types, any top-level cv-qualifiers modifying a parameter type are deleted when forming the function type.

In short, yes, CV-qualifiers are just ignored in that case.