const variable, const pointer and invalid const assignment in c++

This something that regularly shows up when discussing const correctness of a c++ piece of code: see wikipedia const correctness entry, the c++ faq light section 18 and herb sutter item #6.

I wrote this after someone asked me why cont int** cannot be assigned an int**, which seemed a valid conversion. I pointed him to the c++ faq like, section 18 item 17, but It was pretty terse:

The reason the conversion from Foo** → const Foo** is dangerous is that it would let you silently and accidentally modify a const Foo object without a cast:

class Foo {
void modify(); // make some modify to the this object

int main()
const Foo x;
Foo* p;
const Foo** q = &p; // q now points to p; this is (fortunately!) an error
*q = &x; // p now points to x
p->modify(); // Ouch: modifies a const Foo!!

It was hard for him to see why such thing was possible, after all, int* to a const int* is a valid assignment.

If you are comfortable with the const keyword, go directly to the “messing with it” section :-)

OK for now let’s go back to the const keyword itself: it allows a c++ object to be considered as read-only (well, almost …).
The various code snippets were compiled/tested with:

linuxbox:~$ c++ --version
c++ (Ubuntu 4.3.2-1ubuntu12) 4.3.2

syntax first

Most of the confusion when using this keyword comes from how c++ declares variable, the syntax used: machine friendly but definitely not human friendly.

Easy things first:

const int one = 1;
const char letter_a = 'a';

These declaration are straight forward, let’s raise the bar:

const int *p = &one;
const int two = 2;
p = &two;

so far so good: p is pointer to a const int. Both addresses of one and two can be assigned to p. Of course the following is not allowed, since p points to a read-only int:

p = &one;
*p = 2; /* error - pointed object cannot be changed */

now let’s have:

const int * const p = &one;

now p is a pointer to a const int plus p is read-only too:

p = &two; /* error: p cannot be changed */

g++ diagnostic is:

linuxbox:~$ c++ const.cpp
const.cpp: In function ‘int main()’:
const.cpp:11: error: assignment of read-only location ‘* p’

Note how the second const is declared after the star to qualify p as being const it self. This is the trick when reading const declaration (oh and yes, const int one = 1; and int const one = 1; are the same declarations), the star is used as a separator to whom the const is applied to.

We can promote a regular object to a const one:

int a = 1;
int* p = &a;
const int* q = p; // a cannot be modified through q

messing with it

Now it’s time to get ugly, the following promotion won’t work:

const int a = 1;
int *p = 0;
const int ** err = &p;

g++ diagnostic is:

linuxbox:~$ c++ const.cpp
const.cpp: In function ‘int main()’:
const.cpp:20: error: invalid conversion from ‘int**’ to ‘const int**’

and we are like “ugh ?”. And yes, it is very fortunate that the compiler can catch that…
Why ? Otherwise we could change const objects and have undefined behavior without realizing it. Let’s say the previous example is legal:

const int a = 1;
int *p = 0;
const int ** err = &p;
/* chaos from here */
*err = &a;
*p = 2;

The type of *err is const int*, hence this assignment is legal:
*err = &a;

and we know that err = &p;, and that as such *err == p
So we have *err == p and *err = &a, two different types…

Trying to change the value of a through err won’t work, err has the right type:
**err = 2 /* failure: err is const int**, it cannot change a */
BUT we can do it through p now, p has the right type and the promotion allowed us to make it point to the const int a:
*p = 2; /* legal: p has the right type, we lost the trace of constness of a */
We just allowed p to point to const int a and short circuit the type system.

That’s why this type of conversion is not allowed: we don’t want to be able to write such thing without the c++ type system catching it. Now go grab a coffee, you deserve it ;-)

Enjoy and share ;-)


About this entry