References don’t have top-level cv-qualifiers

Sometimes when reading about C++, for instance about template argument deduction, the term “top-level cv-qualifiers” comes up. I just spent an unreasonable amount of time being puzzled by something because I didn’t understand that references don’t have top-level cv-qualifiers. This post will hopefully help the next person (hi, next-year Anders) not make the same mistake.

Looking at

const int&

, I assumed without even thinking about it that the const here was the top-level one. It is not.

First, what’s a cv-qualifier? It’s const, volatile or both. Let’s just use const as an example for this article.

Then, what’s a top-level cv-qualifier? The best way to explain is with an example, and the best example is a pointer. There are two levels of constness to a pointer, the constness of the pointer itself, and the constness of what it’s pointing to.

Given const int *, a non-const pointer to const int, we can visualise it as

pointer (the * part)
const int (the const int part)

And given const int * const, a const pointer to const int, we can visualise it as

const pointer (the * const part)
const int (the const int part)

(And so on, you can imagine how it looks for pointers to non-const int.) The top-level cv-qualifier is the one on the top level, the cv-qualifier on the pointer itself.

Now, how does this look for references?

Given const int&, a reference to const int, we can visualise it as

reference (the & part)
const int (the const int part)

But there’s no such thing as a const reference! Constness applies to the object itself, and a reference is not an object, just an alternative name for an existing object. So there is no such thing as a const int& const, i.e. there’s no such thing as

const reference (the & const part)
const int (the const int part)

Which means, references don’t have top-level cv-qualifiers. The standard even has an example:

Example: The type corresponding to the type-id const int& has no top-level cv-qualifiers.


This is by the way a somewhat recent addition, until this Core Language Defect Report was resolved in 2014, the term “top-level cv-qualifier” was never actually defined in the standard.

Disempower Every Variable

In which I argue you should reduce the circle of influence, and the ability to change, of every variable.

The more a variable can do, the harder it is to reason about. If you want to change a single line of code involving the variable, you need to understand all its other uses. To make your code more readable and maintainable, you should disempower all your variables as much as possible.

Here are two things you can do to minimize the power of a variable:

1: Reduce its circle of influence (minimize the scope)
I once had to make a bugfix in a 400 line function, containing tens of for-loops. They all reused a single counter variable:

  int i;
  for (i = 0; i < n; ++i) {
  for (i = 0; i < n; ++i) {
  //350 lines later...
  for (i = 0; i < n; ++i) {

When looking at a single for-loop, how am I to know that the value of i is not used after the specific loop I was working on? Someone might be doing something like

for (i = 0; i < n; ++i) {
some_array[i] = 23


for (i = 0; i < n; ++i) {
for (; i < m; ++i) {

The solution here is of course to use a local variable to each for-loop (unless of course it actually is used outside of the loop):

for (int i = 0; i < n; ++i) {
for (int i = 0; i < n; ++i) {

Now I can be sure that if I change i in one for-loop, it won’t affect the rest of the function.

2: Take away its ability to change (make it const)

(I have blogged about const a few times before. It is almost always a good idea to make everything that doesn’t need to change const.)

Making a local variable const helps the reader to reason about the variable, since he will instantly know that its value will never change:

void foo() {
  const string key = getCurrentKey();
  (...) //Later...
  (...) //Even later...

Here the reader knows that we are always working with the same key throughout foo().

In summary: Reduce the circle of influence (by reducing the scope) and take away the ability to change (by using const).

If you enjoyed this post, you can subscribe to my blog, or follow me on Twitter.

Show Me Your Signature, and I’ll Tell You Who You Are

When you write your function signatures, you have a choice between passing values, pointers or references. You might be able to make any of them work for the compiler, but what do they tell the user?

Note that even though pointers and reference are somewhat related, and mostly communicate the same thing, they have different suggestions about ownership.


1: Pass by value

void foo(Bar b); I need to copy your object, because I need to modify it, and you don’t want to see the change. (Except for built-in types, which are usually passed by value even though they are not modified by the function.)

2: Pass by reference/pointer

void foo(Bar& b); void foo(Bar* b); I need a reference to your object, in order to modify it, beacuse you need to see the change.

3: Pass by reference to const

void foo(const Bar& b); void foo(const Bar * b); I won’t need to modify your object, and I don’t want to pay the price of a copy.

You could argue that 1 just means I will leave your object alone, and doesn’t say anything about modification. But if you aren’t going to modify it, you should use 3, so I think 1 explicitly states that the object is going to be modified (invisibly to the caller). Unless of course the argument is a built-in type.

Return values

1: Return by value

Bar foo(); Here, take this object and do whatever you want with it, I won’t touch it again. It’s yours.

2: Return a reference

Bar& foo(); There is an object over here that you can use. Someone else might silently change it, though. By the way, I own it, and it is my responsibility to delete it. (If foo() is a non-static member function, you can usually assume that won’t happen until the object on which it is called goes out of scope. If foo() is a static function, you can usually assume this won’t happen until the program exits.)

3: Return a pointer

Bar* foo(); There is an object over here that you can use. Someone else might silently change it, though. By the way, I might delete it at any time. Or maybe that’s your responsibility, and if you don’t, you’ll have a memory leak. But I won’t tell you which one it is! You need more information to be sure, for instance the documentation. Often, you can also deduce ownership from the situation. A singleton retains ownership, a factory does not.

4: Return a reference/pointer to const

const Bar& foo(); const Bar* foo(); There is an object over here that you can use, and I promise it won’t change, even if I still own it. Rules of deletion are as in 2. The reason I don’t list the ownership issues for the pointer in this case, is that I think const is an indication that ownership is retained. If it was not, why use const at all?

A Summary of “const”, Part One

Over the last two weeks, I have mentioned const a couple of times.

const is an often used keyword in C++ (though I would like to see people use it even more), and the different uses can be confusing. In this post I will try to summarize the most common uses:

Constant variables

Constant variables are simple, they cannot be changed after initialization:

const int answer = 43; //Cannot be changed  (even though I know you want to)
answer = 42; //Nope, not today.

Constant pointers

Constant pointers are a bit more involved, as the pointer can either be constant itself, point to something constant, or both, or none:

int x = 1;
const int c = 43;

const int * p1 = &c; //Non-const pointer to something const
p1 = &x; //So we can change what it points to
         //(Also note that a pointer to const can point to something non-const).

int * const p2 = &x; //Const pointer to something non-const.
p2 = c; //Not allowed, cannot change what it points to
*p2 = 2; //But can change the thing it points to

int * p3 = &c; //Cannot point to something const with a pointer to non-const
int * const p4 = &c; //Not even with a const pointer

const int * const p5 = &x; //Const ponter to something const
p5 = &c; //Cannot change what it points to
*p5 = 2; //Cannot change the thing it points to either

//And just to confuse things, it doesn't matter on which side of the type you put const, so the following are both a non-const pointer to a const int:

const int * d = &c;
int const * e = &c;
*d = 42; //Not allowed
*e = 42; //Not allowed

Const and functions

const can also be used with functions:

//Passing arguments as reference to const is a best practice, since this allows
//you to avoid copying, and still promise the caller that his object won't
//be modified.
void f(const string& s);

But there is one more use with const and functions, that is const member functions. Here, const applies to the method itself, not the parameters. A const member function promises to not modify the object on which it is called:

struct Foo {
    int getFoo() const; //Will not modify the object on which it is called
    int getMore(); //Might change the object on which it is called

int doFoo(const Foo& foo) {
    foo.getFoo(); //Ok
    foo.getMore(); //Not ok, cannot call non-const methods on const objects

Functions can also return const variables and pointers, but I’ll cover that in a follow-up where I’ll also cover operators. (Operators are in essence functions, but there is more to say about them.)

Changing the Unchangeable

Last week I asked you to please make member functions const whenever possible. But constant doesn’t always mean constant.

Declaring an object const is a way to tell the compiler that this object cannot be changed whatsoever. What you actually try to express might however be a logical immutability, that the meaning of the object should not change. That doesn’t necessarily imply that none of its member variables can be changed.

The most common example is to cache the result of an expensive operation. To expand on last weeks example:

class Whisky {
  Smell smellIt() const;
  Taste tasteIt(int ml);

Imagine that computing the smell of the whisky is a complicated operation [1], that you don’t want to do every time someone smells it. You might want to introduce a private member to hold a cached description of the smell. This member must be possible to change, to be able to write the cached value. Computing and storing this value does however not logically modify the object, so it should be possible to do even for const objects. To allow for this, use the mutable keyword:

class Whisky {
  Smell smellIt() const;
  Taste tasteIt(int ml);
  mutable Smell* smell;

The definition of smellIt() would now look something like this:

Smell Whisky::smellIt() const {
  if (!smell)
    smell = computeSmell(); //Assume this function exists and returns a new, dynamically allocated Smell object
  return *smell;

Exactly how you do the caching is up to you, but if you use a simple pointer (and not for instance a shared_ptr), you must remember to delete smell in ~Whisky().

Please Make Member Functions Const Whenever Possible

When you write a member function that doesn’t modify the object it operates on, please make sure to make it const.

class Whisky {
  Smell smellIt() const;
  Tase tasteIt(int ml);

Smelling the whisky doesn’t modify it, so I made smellIt() const. Tasting does however modify it (there is less left in the glass), so tasteIt() is not const.

Why does this matter? If someone makes a const object of the class, they might still want to call some methods on it. For instance, a good practice for a method is to take objects by reference to const. And then you will only be able to call const methods:

void describeWhisky(const Whisky& whisky) {
  std::cout << whisky.smellIt();

This would not be possible if smellIt() was not const. The same goes for using const_iterators and a lot of other situations, so please remember to add const whenever you can, even though it doesn’t make a differece to you then and there.

Edit: I posted a follow up, on how you can change some parts of an object, even if it is const.