C++ offers concise, precise and utterly bewildering usage for the const modifier on types. Enough so that this note could just as well have been a part of the freakshow. Beginning C++ programmers often ask themselves what a "const pointer" should be.

"It depends".

Assume these declarations:

T* q;
T gimme_T;
  • If you want the variable holding the pointer not to change its value (a constant pointer), you need to say T* const p = &some_T;. Now p = q; is an error (you must initialize p, but you cannot assign to p), but *p = gimme_T; is fine.
  • If you want the object at which the pointer points not to change its value (a pointer to constant data), you need to say const T* p;. Now p = q; is fine (p will look at the same thing as q does), but *p = gimme_T(); is an error: you cannot modify via p;.
  • To make for interesting interview questions, as well as for actual use in the field, the form const T* const p also exists. This declares p as a constant pointer to constant data. Both p=q; and *p = gimme_T(); are not allowed.
  • Since things are too simple for an industrial grade language like C++, const T means the same thing as T const. Other than that, C++ const declarations follow the old C "inside-out" declaration syntax.

C++ also allows you to declare a class method as "const". This qualifier indicates that the method may be used on a const-qualified reference to the type:

// not the real declarations, which are far more baroque!
template<typename T>
class vector<T> {
  // ...
public:
  // ...
  typedef something iterator;
  typedef something else const_iterator;
  // ...
  iterator begin();
  const_iterator begin() const;
  iterator end();
  const_iterator end() const;
  // ...
};
Gorgonzola points out that this is semantic overloading at its best.

If a const vector<double> (1) allowed you to get an iterator pointing at its first element (2), then you could subvert constness and modify that element (3) by saying

const vector<double> v = gimme_vector(); // (1)
vector<double>::iterator i = v.begin();  // (2)
*i = 3.14159265;                         // (3)
Obviously, we can't have that. But examining the method declarations above shows that a const vector only has a begin() method returning a const_iterator. So the assignment in (2) cannot be instantiated, giving a compilation error. You could instead say in (2) "vector<double>::const_iterator i = v.begin();", but then (3) would fail -- dereferencing a const_iterator gives a const reference, which isn't assignable!

The const modifier for methods lets us make this important distinction at the right place. Of course, it does mean that C++ does with const what it rightly slams C for doing with static -- hideous semantic overloading, of vaguely-related concepts.


Modern ISO C also has this modifier. It is almost, but not quite, wholly unlike the modifier in C++. The general rules for its use are similar to those for C++, except...

  • There is no overloading of functions by type, therefore no overloading by constness. So the declaration of the standard function strchr is
    char *strchr(const char *s, int c);
    
    which is nonsense!

    strchr takes a pointer to a constant string, and returns a pointer to a string -- it allows you (from the type-checking point of view, though not really, because the language forbids it...) to convert a constant string to a mutable string.

    It has to do this: we want to call strchr on constant strings, so its argument must have type const char *. And we want to be able to modify via its return value pointer when called with a mutable string, so its return value must have type char *. Oops.

    C++ solves the problem by overloading on the argument type. This is also ugly, but less, and in different ways.

  • const variables are really variables not constants.
  • To get a real constant, at least for an integer, you need to use an enum (similar to the C++ enum hack).