C++ Constants with const and constexpr

Learn about constants in C++, and how we can use the const and constexpr keywords to make our code better
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

3D art showing a fantasy maid character
Ryan McCombe
Ryan McCombe
Posted

Constants are things that do not change. In C++, we generally express that requirement with the const keyword.

In this lesson, we will explain how they can be used in many other situations. The const keyword can be used in a lot of different ways in C++. The exact effect depends on the context. Lets see some examples.

const Member Functions

Lets imagine we have this class:

class Character {
public:
  int GetHealth() {
    return Health;
  };

private:
  int Health { 100 };
};

In the above example, our class has a GetHealth() function that does not modify any properties of the Character object.

These type of functions can therefore be marked as const.

We can do this by adding the const keyword to the function signature:

class Character {
public:
  int GetHealth() const {
    return Health;
  };

private:
  int Health { 100 };
};

When declaring and defining a function seperately, such as in a header and cpp file, the const specifier works in the same way as other keywords, like virtual and override.

That means, we only need to make the declaration as const:

// Character.h
class Character {
public:
  int GetHealth() const;

private:
  int Health { 100 };
};
// Character.cpp
int Character::GetHealth() { return Health; }

Marking a function as const achieves two main goals:

  • it provides a quick way for anyone reading our function code to realise that it will not modify the object
  • It makes the compiler check for errors. If we do try to modify the object within this function, we get a compiler error, rather than potentially introducing a bug.

const with C++ Variables

We can declare any variable as const:

const float Armour { 0.2f };

// Cannot change a const variable
Armour = 0.5f;

Member variables can also be const:

class Character {
public:
  const int Level { 1 };
};

Character Frodo;

// Not allowed - Level is const
Frodo.Level++;

const with C++ Objects

Entire objects that we create from a class can be const. With const objects, we cannot change any variables, even if the class defines them as public and not const.

We also cannot call any functions on that object, unless the function is also const.

class Character {
public:
  int Health { 100 };
  void TakeDamage(int Damage) { Health -= Damage; }
  void GetHealth() { return Health; }
  float GetArmour() const { return 0.2; }
};

const Character UnchangableCharacter;

// Not allowed as the object is const
UnchangableCharacter.Health -= 50;

// We also can't call a function that is not const
UnchangableCharacter.TakeDamage(50);

// GetHealth doesn't change the object
// But is not marked as const, so we can't call it
UnchangableCharacter.GetHealth();

// But we can call a const function
UnchangableCharacter.GetArmour();

C++ mutable Members

We can specify the mutable keyword on any variable defined within our classes. Mutable variables can be modified by const functions.

The use case for this is inherently quite niche and, often times, using it at all indicates a flaw with our design. This is particularly true if we're marking variables that are core to the functionality of our class mutable.

However, it can sometimes be helpful for quick debugging, or if our classes carry ancillary data.

For example, we could keep track of how many calls are made to our const function like this:

class Character {
public:
  int GetHealth() const {
    HealthRequests++;
    return Health;
  }
private:
  int Health { 100 };
  mutable int HealthRequests { 0 };
};

const C++ Parameters

Our sections that talked about passing function arguments as references mentioned the ability to pass those references as const. We can do the same with all function arguments, including values and pointers.

void TakeDamage(const int Damage, const Character* Instigator);

In the body of this function, we can not change Damage or Instigator

The const keyword is most important when dealing with function arguments that are references or pointers. When we pass a parameter by value, our function creates a copy of that variable.

Code calling our function is generally not going to care what happens to a copy of it's data. Therefore, it's fairly common to not mark these things as const, even if they aren't being modified.

const C++ References

As we've seen earlier, passing a reference to a function allows that function to modify a value that is outside of its scope.

void IncrementByRef(int& x) {
  x++;
}

When we, as developers, write code that passes a reference to a function, one of the first things we will want to know is whether our value is going to be modified as a result of passing its pointer to that function.

We can use const references in the parameter list to clarify whether or not that is going to happen.

void SomeFunction(const int& x) {
  // We can't modify the value at x
  x++; // not allowed
}

When we see a function that accepts a reference to a const value, we know that our value is unlikely be changed as a result of that function call.

void SomeFunction(const int& x) {};

int x = 1;
SomeFunction(&x);

In the above example, we know the value contained in x won't be changed by the call to SomeFunction(), because SomeFunction has said it will be treated as a reference to a const variable.

Note, we didn't initialise x as a constant. In our call to SomeFunction, we are passing it a non-constant integer, but SomeFunction's argument list is saying it will be a reference to a const integer.

This is totally fine - our function is just going to treat the reference as if it is a const int, even if x wasn't initialised as a const.

The use of const in this example is a promise that, whether x is const or not, it will not be modified as a result of calling this function.

Note, the opposite is not true. Had we initialised x as a const int, we can only pass it as a reference to parameters that are explicitly marked as const. The below example violates this, and will therefore not compile:

void SomeFunction(int& x) {}

const int x = 1;
SomeFunction(&x);

Hopefully this makes sense from a logical perspective. The first scenario is passing a variable that can be modified to a function that is promising not to modify it. There is no conflict there.

However, in the second example, there is a problem. The variable type says the value can't be modified, but the parameter type suggests the function might try to. So, the compiler objects to this conflict.

const C++ Pointers

An interesting property comes up when considering const from the perspective of pointers. Consider the following code:

1int x { 100 };
2int y { 200 };
3const int* Pointer { &x };
4
5*Pointer += 50;
6Pointer = &y;
7

Line 5 is prevented as we might expect. But, perhaps surprisingly, line 6 is totally acceptable.

When we put const before the *, we are saying that the value being pointed at cannot be changed through the pointer (line 5) but the pointer itself can be updated to point at something else (line 6).

This is also the case if we put the const between the data type and the *. The following two statements are equivalent:

const int* Pointer { &x };
int const* Pointer { &x };

What we are creating here is sometimes called a "pointer to a const int". The integer is constant, but the pointer is not.

What if we wanted the opposite? A "constant pointer to an int"?

If we wanted to prevent the pointer from being updated to point to something else, we would move the const after the *:

int x { 100 };
int y { 200 };
int* const Pointer { &x };

*Pointer += 50;
Pointer = &y;

Now, we can change the value being pointed at (line 5) but can no longer make the pointer point at something else (line 6).

What if we wanted both to be constant? That is, what if we wanted a "constant pointer to a constant int"? We do this by using the const specifier twice in our data type:

int x { 100 };
int y { 200 };
const int* const Pointer { &x };

*Pointer += 50;
Pointer = &y;

To summarise:

  • pointer to int: int* MyNum
  • const pointer to int: int* const MyNum
  • pointer to const int: int const* Num
  • const pointer to const int: int const* const MyNum

Compile Time Constants and constexpr

We can imagine there being two types of constant variables - those that are known when we build our software, and those that can't be known until our software runs.

Almost all of the variables we've seen so far have been known at compile time, and therefore could be compile time constants. An example of a compile time constant is something like this:

const float Gravity { 9.8 };

Many other variables cannot be known until the program is run. Examples of these variables might include:

  • the time the application was launched
  • information about the user's hardware
  • anything that is based on user input

The more things that can be done at compile time, the more performant our software will be. Compilers can often detect when our code is creating compile time constants, and optimize them for us automatically.

However, we can make sure that a variable is a compile time constant (and have the compiler alert us if it doesn't seem to be) by using the constexpr (short for constant expression) keyword.

constexpr float Gravity { 9.8 };

With all the different uses of const under our belt, lets use the next lesson to briefly talk about the scenarios we should use it, and some scenarios where we shouldn't.

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Clean Code
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access!

This course includes:

  • 66 Lessons
  • Over 200 Quiz Questions
  • Capstone Project
  • Regularly Updated
  • Help and FAQ
Next Lesson

The Importance of const Correctness

Learn more about the use of const and why we should use it throughout our application.
3D art showing a fantasy maid character
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved