const vs constexpr in C++

CMP
4 min readSep 15

--

Both const and constexpr are keywords used to specify that a value cannot be modified, but they have some differences in functionality and use cases.

What is const?

const is a fundamental keyword in C++ used for defining constant variables, meaning the value of the variable cannot change. Look at the following simple example of this concept:

int main() {
const int a = 0;
a = 5; // error

return 0;
}

In addition to constant variables, const can be used for member functions. Look at the following class:

class MyClass {
public:
void SetValue(int newValue);
int GetValue() const;

private:
int m_value;
}

void MyClass::SetValue(int newValue) {
m_value = newValue;
}

int MyClass::GetValue() const {
return m_value;
}

In this example, GetValue() is defined as const, which promises that m_value will remain unmodified within the GetValue() function. In contrast, SetValue(int newValue) is not const, so m_value is allowed to be modified.

Consider the following modification:

void MyClass::SetValue(int newValue) const {
m_value = newValue; // error
}

Here, SetValue(int newValue) is defined as const, which results in an error because an object’s state cannot be modified within its own const member functions.

Another important feature of const is that both const and non-const member functions can be called by non-const variables, but const variables can only call const functions. For example:

int main() {
MyClass obj1;
const MyClass obj2;

obj1.SetValue(10); // No error
int obj1Value = obj1.GetValue(); // No error

obj2.SetValue(10); // Error
int obj2Value = obj2.GetValue(); // No error

return 0;
}

In this example, we observe that the const MyClass obj2 variable is not allowed to call the non-const member function. This makes sense, because if we want obj2 to be const, then we intend for it to remain constant, so changing its state with a setter function would be invalid.

Personally, here is how I would define MyClass in most cases:

class MyClass {
public:
void SetValue(int newValue);
const int& GetValue() const;

private:
int m_value;
}

In most cases, a getter function is used to get a read-only version of an object’s state. Although some cases may require it, you generally should avoid using a getter function to get a value that you plan to modify. Therefore, making GetValue() a const function, as well as returning a const reference is the most efficient and best practice (even though returning a reference to an int is kind of unnecessary but good practice for returning objects).

What is constexpr?

Beginning in C++11, constexpr was added to the C++ standard and later improved in C++14. It is used to declare constant expressions, hence the name constexpr. These expressions are very similar in functionality to const; in fact, all constexpr variables are const by default. However, there are a few key differences.

The most important difference is that constexpr values are initialized at compile-time rather than runtime. This means that a constexpr value can must be known at compile-time or determined by another constexpr value. If you are trying to hyper-optimize your code, opt for compile-time constexpr values whenever possible.

Furthermore, constexpr can be applied to functions. This is useful for instances where a calculation needs to be performed but will always remain the same, such as the following implementation of a Fahrenheit to Celsius conversion function:

constexpr double FahrenheitToCelsius(double fahrenheit) {
return (fahrenheit - 32.0) * 5.0 / 9.0;
}

constexpr double c_waterBoilingPointFahrenheit = 212.0;
constexpr double c_waterBoilingPointCelsius = FahrenheitToCelsius(c_waterBoilingPointFahrenheit);

This calculation is determined at compile-time rather than runtime like it would be if FahrenheitToCelsius was defined as a standard function.

Another great example of constexpr is to use a constexpr array for a lookup table. For example:

constexpr int Factorial(int n) {
return (n <= 1) ? 1 : n * Factorial(n - 1);
}

int main() {
constexpr int factorialTable[10] = {
Factorial(0), Factorial(1), Factorial(2), Factorial(3), Factorial(4),
Factorial(5), Factorial(6), Factorial(7), Factorial(8), Factorial(9)
};

constexpr int factorialOfFive = factorialTable[5];

return 0;
}

With a lookup table, we only need to calculate values once at compile-time, which improves performance in cases where a value may normally need to be calculated multiple times.

Conclusion

Both const and constexpr are important features of modern C++ development that should be used extensively.

Any time you expect a value to remain constant, like when retrieving the value of a member variable of an object using a getter function, you should use const — for both the variable used to store the value and in the getter function itself.

If you need to define a constant value, such as a buffer size or the number of degrees in a circle, it is best practice to use constexpr, ideally with the c_ prefix, similar to using m_ for member variables. If your constant needs to be calculated but will always have the same result, then the calculation function should also be constexpr to take advantage of the compile-time speed.

--

--