Reputation: 193
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
Upvotes: 19
Views: 5361
Reputation:
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.
Upvotes: 3
Reputation: 56567
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.
Upvotes: 4
Reputation: 109189
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.
Upvotes: 9
Reputation: 9617
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.
Upvotes: 5