Channels ▼

Matthew Wilson

Dr. Dobb's Bloggers

The Mutable Comprehension of const

June 05, 2010

I don't know about you, but I get all future-shocked when trying to express or understand concepts where language is used ambiguously. Unfortunately, even after all these years, it seems that the use of const as a modifier of pointer/reference types in C and C++ can cause confusion.I've been working with some talented C++ developers recently, and have been surprised to learn that confusion over the use of the const modifier, when applied to pointers and references, still persists even among programmers of considerable experience. I think there are three reasons why this is so: language, syntax, and the C++ Standard.

Language

In my opinion, the key to surviving the ambiguity of const pointers and references is to not use the word "const" at all, and instead rely on the use of precise nomenclature about what is actually implied by its use: mutability.

The problem with "const" is that it's just not specific enough. When someone says "const pointer" it's not clear whether the pointer is "const" (i.e. it must always points to the same location) or the thing it points to is const(ant) (i.e. the variable to which it points cannot be changed).

This is further complicated by the fact that a variable referred to by a "const" pointer may be changed by another (non-const) alias to it elsewhere in the program. It's actually impossible and inappropriate to infer anything that a variable is const from a pointer to it.

Rather than this imprecision and ambiguity, I suggest programmers should eschew phrases such as "const pointer", "non-const pointer", "pointer to const int", "pointer to non-const int", and "const pointer to const int". Prefer instead the unambiguous phrases "immutable pointer", "mutable pointer", "non-mutating pointer to int", "mutating pointer to int", and "immutable non-mutating pointer to int". A pointer may or may not be mutable (whether the pointer may be changed to point to a different location) and may or may not be mutating (whether it may be used to change the variable to which it refers).

(Note: in this way, the always-confusing terminology of the "const reference" goes away. Strictly speaking, a reference is always const, because references cannot be reassigned. In the new terminology, references are immutable. Which we knew. Now we need only concern ourselves with whether a reference is mutating or non-mutating.)

Syntax

C and C++ are agnostic about where the const modifier can be placed in a majority of contexts. The following pairs of constructs are identical:

  // 1. int
  const int i = 10;
  int const j = 20;

  // 2. pointer-to-int
  const int* p = &i;
  int const* q = &j;

  // 3. reference-to-int
  const int& p = i;
  int const& q = j;

I prefer the second form of each, and I strongly recommend it to C/C++ programmers, because (I believe that) it's the only way in which one can sensibly read composite types, by reading from right-to-left. Consider the following two equivalent ways of specifying a pointer:

  const int* const p = &i;
  int const* const q = &j;

Both p and q are immutable non-mutating pointers to int. If we read from right-to-left, the presence/absence of const before (i.e. to the right of) the * determines whether the pointer is mutable/immutable, and the presence/absence of const after (i.e. to the left of) the * determines whether the pointer is mutating/non-mutating. By always preferring the X const form over the const X form, the const, if present, will be immediately adjacent to the *. Doing the same for non-pointer/non-reference variable declarations follows for reasons of consistency.

Let's do a few more to practise:

  // A mutable mutating pointer to int
  int* p = &i;

  // A mutable non-mutating pointer to int
  int const* p = &i;

  // An immutable mutating pointer to int
  int* const p = &i;

  // An immutable non-mutating pointer to int
  int const* const p = &i;

  // An (immutable) mutating reference to int
  int& r = &i;

  // An (immutable) non-mutating reference to int
  int const& r = &i;

The C++ Standard

Historically, the C++ standard is not much of a friend to us in so far as precise names go, and that is also the case in this regard. There are the well known obvious ambiguities, such as the inconsistency between empty() (an interrogative: is the instance empty?) and erase() (an imperative: clear the instance contents). With member types of pointers, references, and iterators, the use of the word const as part of essential member types const_reference, const_pointer, and, particularly, const_iterator, gets more confusing.

It's not only possible to change a const_iterator, it's actually essential to traversing ranges. Furthermore, with some containers (such as C++0x sets), erasing elements is done via a const_iterator. A const_iterator is not an immutable iterator, it's a non-mutating iterator: an iterator through which one may access an element in a non-mutating manner, and with which one may traverse the sequence to access, in a non-mutating manner, other elements in the container/collection.

Language, Syntax, The C++ Standard: How to Survive

My way to survive these issues are:

  • Syntax: always apply the const modifier after the type being modified. I've been an adherent to this for pointers and references for many years, but have only recently gone the whole hog in applying it to all types of variables consistently. I'm finding it beneficial.
  • Language: when writing documentation or articles (or blogs, or books), I always refer to pointers/references/iterators in terms of their being mutable/immutable and mutating/non-mutating
  • The C++ Standard: the fat lady has sung on this one. Don't bother trying to create container types with a mutating called empty(), or with mutating_iterator member types. There's just too much mindshare and technical momentum to buck it.

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video