In C++, const, volatile, and mutable are type qualifiers that modify the behavior of variables.
const
const specifies that a variable’s value cannot be changed after initialization. Any attempt to modify a const variable will result in a compile-time error.
volatile
volatile indicates that a variable’s value can be changed by external factors outside the control of the program, such as hardware or another thread. It prevents the compiler from performing optimizations that might assume the variable’s value remains constant.
mutable
mutable applies only to class members and allows modification of a member variable even within a const object. It is useful when a class has internal state that needs to be updated without affecting the object’s externally visible const-ness.
mutable may not be used in combination with static or const
Some C++ Specification Jargon
Here is some jargon from the C++ specification which may be a little esoteric, but you may find this jargon used in some compiler error and warning messages.
cv (const and volatile) type qualifiers
Any (possibly incomplete) type other than function type or reference type is a type in a group of the following four distinct but related types:
A cv-unqualified version.
A const-qualified version.
A volatile-qualified version.
A const-volatile-qualified version.
Array types are considered to have the same cv-qualification as their element types.
A const volatile object is:
an object whose type is const-volatile-qualified,
a non-mutable subobject of a const volatile object,
a const subobject of a volatile object, or
a non-mutable volatile subobject of a const object.
Behaves as both a const object and as a volatile object.
Examples
The following program exhibits a number of compile-time errors:
//// main.cpp// const, volatile, mutable//// Created by Bryan Higgs on 2/10/25.//#include <iostream>classExample{public:Example(inta, intb, intc) : constVar(a), nonConstVar(b), volatileVar(c), mutableVar(0) {}voidmodify() {nonConstVar = 10; // OKconstVar = 20; // Cannot assign to non-static data member 'constVar' with const-qualified type 'const int'// Non-static data member 'constVar' declared constvolatileVar = 30; // OKmutableVar = 40; // OK }voidconstModify() const {nonConstVar = 10;constVar = 20; // Cannot assign to non-static data member within const member function 'constModify'// Member function 'Example::constModify' is declared constvolatileVar = 30; // Cannot assign to non-static data member within const member function 'constModify'// Member function 'Example::constModify' is declared constmutableVar = 40; // OK to modify a mutable member in a const member function }private:intnonConstVar;constintconstVar;volatileintvolatileVar;mutableintmutableVar;};intmain(intargc, constchar * argv[]){constExample constObj(1, 2, 3);constObj.constModify();Exampleobj(4, 5, 6);obj.modify();volatileintvol = 7;constvolatile int constVol = 8;vol = 9; // OKconstVol = 10; // Cannot assign to variable 'constVol' with const-qualified type 'const volatile int'// Variable 'constVol' declared constintx = vol; // Compiler must read the current value of volinty = constVol; // Compiler must read the current value of constVolreturn0;}
constexpr Specifier (since C++11)
The idea of constexpr is to provide a performance improvement for programs by doing computations at compile time rather than run time. Note that once a program is compiled and finalized by the developer, it is run multiple times by users. The idea is to spend time in compilation and save time at run time (similar to template metaprogramming – see later…).
constexpr specifies that the value of an object or a function can be evaluated at compile-time and the expression can be used in other constant expressions. Such entities can then be used where only compile time constant expressions are allowed (provided that appropriate function arguments are given).
A constexpr specifier used in an object declaration or non-static member function (until C++14) implies const.
A constexpr specifier used in the first declaration of a function or static data member (since C++17) implies inline. If any declaration of a function or function template has a constexpr specifier, then every declaration must contain that specifier.
It defines an expression that can be evaluated at compile time.
Constant expressions can be used as non-type template arguments, array sizes, and in other contexts that require constant expressions, for example:
intn = 1;std::array<int, n> a1; // Non-type template argument is not a constant expression// Read of non-const variable 'n' is not allowed in a constant expressionconstint cn = 2;std::array<int, cn> a2; // OK: “cn” is a constant expression
NOTE: We talk about templates in detail later, so, until then, bear with us…
constexpr variable
A variable or variable template (since C++14) can be declared constexpr if all following conditions are satisfied:
The declaration is a definition.
It is of a literal type.
It is initialized (by the declaration).
constexpr Example
Here is a simple example of the use of constexpr usage:
//// main.cpp// const, volatile, mutable//// Created by Bryan Higgs on 2/10/25.//#include <iostream>constexprintsize = 5;intarr[size]; // Valid, size is known at compile timeconstexprintsquare(intx){returnx * x;}intmain(intargc, constchar * argv[]){constexprintresult = square(size); // evaluated at compile time std::cout << "The square of " << size << " is " << result << std::endl;intnum = 10;intruntime_result = square(num); // evaluated at runtime std::cout << "The square of " << num << " is " << runtime_result << std::endl;return0;}
The program outputs:
The square of 5 is 25
The square of 10 is 100
Program ended with exit code: 0
constexpr Example with a Class
Here’s an example of constexpr used with a class:
//// main.cpp// const, volatile, mutable//// Created by Bryan Higgs on 2/10/25.//#include <iostream>// 3. constexpr with classclassRectangle {public:constexprRectangle(intw, inth) : width(w), height(h) {}constexprintarea() const { returnwidth * height; }private:intwidth;intheight;};intmain(intargc, constchar * argv[]){constexprRectanglerect(5, 10);constexprintrect_area = rect.area(); // evaluated at compile time std::cout << "The area of the rectangle is: " << rect_area << std::endl;return0;}
This program outputs:
The area of the rectangle is: 50
Program ended with exit code: 0
constexpr Example to Calculate the Size of an Array at Compile Time
constexpr Example to Convert from One Unit to Another
//// main.cpp// const, volatile, mutable//// Created by Bryan Higgs on 2/10/25.//#include <iostream>#include <cmath> // For standard declaration of pi (M_PI)constexprdoubleConvertDegreeToRadian(constdouble& degrees){return (degrees * (M_PI / 180));}intmain(intargc, constchar * argv[]){doubleangleInRadians = ConvertDegreeToRadian(90.0); std::cout << "Angle in radians: " << angleInRadians << std::endl;return0;}
which outputs:
Angle in radians: 1.5708
Program ended with exit code: 0
constexpr Example to Calculate a Value in the Fibonacci Sequence
The Fibonacci Sequence
In mathematics, the Fibonacci sequence is a sequence in which each element is the sum of the two elements that precede it. Numbers that are part of the Fibonacci sequence are known as Fibonacci numbers, commonly denoted Fn .
The Fibonacci numbers may be defined by the recurrence relation:
and
for n > 1.
//// main.cpp// const, volatile, mutable//// Created by Bryan Higgs on 2/10/25.//#include <iostream>constexprintfibonacci(intn) {return (n <= 1) ? n : fibonacci(n-1) + fibonacci(n-2);}intmain(intargc, constchar * argv[]){constexprintresult = fibonacci(10); std::cout << "Fibonacci(10) = " << result << std::endl;return0;}
which outputs:
Fibonacci(10) = 55
Program ended with exit code: 0
NOTE: There is a limit (specific to each C++ compiler) to the evaluation of constexpr compile-time expressions. In the above case, the expression at line 17 is a call to a recursive function. It succeeds for a value of 10 (in my compiler), but if I change the value to 30, I get the following:
Constexpr variable ‘result’ must be initialized by a constant expression Constexpr evaluation hit maximum step limit; possible infinite loop?
Some compilers may crash instead of emitting such a compile-time error.
consteval and constinit Specifiers (since C++20)
consteval Specifier
The consteval specifier declares a function that must be evaluated at compile time. Any call to a consteval function must occur in a context where a constant expression is required.
Example:
//// main.cpp// const, volatile, mutable//// Created by Bryan Higgs on 2/10/25.//constevalintmultiply(intx, inty) {returnx * y;}intmain(intargc, constchar * argv[]){constexprintresult1 = multiply(5, 3); // OK: evaluated at compile timestatic_assert(result1 == 15);inta = 5;intresult2 = multiply(a, 3); // Call to consteval function 'multiply' is not a constant expression// Read of non-const variable 'a' is not allowed in a constant expressionreturn0;}
constinit Specifier
The constinit specifier ensures that a variable with static or thread storage duration is initialized at compile time. It guarantees that the initialization is either zero initialization or constant initialization.
In C++ Formal Language
If a variable is declared with constinit, its initializing declaration must be applied with constinit. If a variable declared with constinit has dynamic initialization (even if it is performed as static initialization), the program is ill-formed.
If no constinit declaration is reachable at the point of the initializing declaration, the program is ill-formed, no diagnostic required.
constinit cannot be used together with constexpr. When the declared variable is a reference, constinit is equivalent to constexpr. When the declared variable is an object, constexpr mandates that the object must have static initialization and constant destruction and makes the object const-qualified, however, constinit does not mandate constant destruction and const-qualification. As a result, an object of a type which has constexpr constructors and no constexpr destructor (e.g. std::shared_ptr<T>) might be declared with constinit but not constexpr.
Got that?
The constinit specifier is used to mark variables that must be initialized with compile-time constant expressions or constant-initialized constructors and it also ensures that the initialization will be done during the static initialization phase. It prevents the variables with static storage duration to be initialized at runtime. The variables specified with constinit specifier need to be initialized with a constant expression.
//// main.cpp// const, volatile, mutable//// Created by Bryan Higgs on 2/10/25.//constevalintget_value() {return42; // The "answer"}constinitintvalue1 = get_value(); // OK: Initialized at compile timeconstexprintvalue2 = 100;constinitintvalue3 = value2; // OK: Initialized at compile time using constexprconstinitconstexprintvalue4 = value2; // Cannot combine with previous 'constinit' declaration specifierintmain(intargc, constchar * argv[]){staticconstinitintvalue5 = 123; // OK: static storage durationreturn0;}