A constant expression is an expression that can be evaluated at compile-time. The const qualifier gives a weak guarantee of a constant expression – a const-qualified type may not be changed after initialisation but that does not guarantee it will be initialised at compile-time. For example:
C++11 introduces a strong form of constant expression, constexpr, which also expands the capabilities of compile-time evaluation.
A constexpr variable is essentially the same as qualifying the type as const with the additional requirement that its initialiser(s) must be known at compile-time. Any object type that could be made const can be made a constexpr object. As expected, the object must be initialised on definition. Note that literals are, by definition, constant expressions.
Since constexpr types have their value known at compile-time they can be placed in read-only memory; and, for built-in types (and provided you don’t take their address) the compiler doesn’t even have to store the object. We’ll look at user-defined (class)types in a moment.
So far, so what?
The real power of constant expressions comes from the fact that not only objects but functions may be qualified as constexpr.
A constexpr function is one that may be evaluated at compile-time; and hence used in the definition of other constant expressions:
Of course, there are some limitations with constexpr functions:
- They must have exactly one return statement
- They cannot have local variables
- They cannot have conditional expressions
- They cannot throw exceptions
- They cannot include a goto statement
- All parameters must be (capable of being) constexpr objects
Put simply, in order to be a constexpr function everything the function needs to be computed must be known at compile-time. Making a function a constant expression implies it is inline.
(Note: C++14 relaxes some of these requirements with respect to local variables and conditionals).
Marking a function as a constexpr doesn’t limit it to only be usable at compile-time. They may still be used at run time:
The use of constexpr for functions extends to member functions, too. Marking a member function as a constexpr implicitly makes it a const function, meaning it cannot change any of the attributes of the class.
This is also true of constructors. Making a class’s constructor a constexpr allows objects of that type to themselves be constexpr objects.
Since everything required to construct the Rommable object is known at compile-time it can be constructed in read-only memory.
In order to be truly ROM-able there are some additional requirements we must satisfy (over and above the requirements for a constexpr function):
- The class cannot have any virtual functions (virtual functions cannot be constant expressions)
- The class cannot have a virtual base class
- All base classes and all const members must be initialised by the constructor
Can’t wait? Download the full set of articles as a PDF, here.
To learn more about Feabhas’ Modern C++ training courses, click here.
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.