Using constexpr to Improve Security, Performance and Encapsulation in C++
constexpr is a new C++11 keyword that rids you of the need to create macros and hardcoded literals. It also guarantees, under certain conditions, that objects undergo static initialization. Danny Kalev shows how to embed constexpr in C++ applications to define constant expressions that might not be so constant otherwise.
The new C++11 keyword constexpr controls the evaluation time of an expression. By enforcing compile-time evaluation of its expression, constexpr lets you define true constant expressions that are crucial for time-critical applications, system programming, templates, and generally speaking, in any code that relies on compile-time constants.
NOTE: I use the phrases compile-time evaluation and static initialization in this article interchangeably, as well as dynamic initialization and runtime evaluation, respectively.
Timing is Everything
Before discussing constexpr, I need to clarify the difference between traditional const and the new constexpr.
As we all know, const guarantees that a program doesn’t change a variable’s value. However, const doesn’t guarantee which type of initialization the variable undergoes. For instance, a const variable initialized by a function call requires dynamic initialization (the function is called at runtime; consequently, the constant is initialized at runtime). With certain functions, there’s no justification for this restriction because the compiler can evaluate the function call statically, effectively replacing the call with a constant value. Consider:
const int mx = numeric_limits
The function max() merely returns a literal value. However, because the initializer is a function call, mx undergoes dynamic initialization. Therefore, you can’t use it as a constant expression:
int arr[mx]; //compilation error: “constant expression required”
A similar surprise occurs when the initializer of a class’s const static data member comes “too late”:
struct S {
static const int sz;
};
const int page_sz = 4 * S::sz; //OK, but dynamic initialization
const int S::sz = 256; //OK, but too late
Here the problem is that the initializer of S::sz appears after the initialization of <page_sz. Consequently, page_sz undergoes dynamic initialization. That isn’t just slower than static initialization; it also disqualifies S::sz from being used as a constant integral expression, as the following example shows:
enum PAGE
{
Regular=page_sz, //compilation error: “constant expression required”
Large=page_sz*2 //compilation error: “constant expression required”
};
Problems with Pre-C++11 Workarounds
In pre-C++11 code, the common workaround for the late function call problem is a macro. However, macros are usually a bad choice for various reasons, including the lack of type-safety and debugging difficulties. C++ programmers thus far were forced to choose between code safety (i.e., calling a function and thereby sacrificing efficiency) and performance (i.e., using type-unsafe macros).
With respect to a const static class member, the common workaround is to move the initializer into the class body:
struct S {
static const int sz=256;
};
const int max_sz = 4 * S::sz; // static initialization
enum PAGE
{
Regular=page_size, //OK
Large=page_size*2 //OK
};
However, moving the initializer into the class body isn’t always an option (for example, if S is a third-party class).
Another problem with const is that not all programmers are aware of its subtle rules of static and dynamic initialization. After all, the compiler usually doesn’t tell you which type of initialization it uses for a const variable. For example, mx seems to be a constant expression when it isn’t.
The aim of constexpr is to simplify the rules of creating constant expressions by guaranteeing that expressions declared constexpr undergo static initialization when certain conditions are fulfilled.
Constant Expression Functions
Let’s look at numeric_limits
#define INT_MAX (2147483647)
class numeric_limits
{
public:
inline static inline int max () { return INT_MAX; }
};
Technically, a call to max() could make a perfect constant expression because the function consists of a single return statement that returns a literal value. C++11 lets you do exactly that by turning max() into a constant expression function. A constant expression function is one that fulfills the following requirements:
- It returns a value (i.e., it isn’t void).
- Its body consists of a single statement of the form
return exp;
where >expr is a constant expression.
- The function is declared constexpr.
Let’s examine a few examples of constant expression functions:
constexpr int max()
{return INT_MAX;} //OK
constexpr long long_max()
{ return 2147483647; } //OK
constexpr bool get_val()
{
bool res=false;
return res;
} //error, body isn’t just a return statement
Put differently, a constexpr function is a named constant expression with parameters. It’s meant to replace macros and hardcoded literals without sacrificing performance or type safety.
constexpr functions guarantee compile-time evaluation so long as their arguments are constant expressions, too. However, if any of the arguments isn’t a constant expression, the constexpr function may be evaluated dynamically. Consider:
constexpr int square(int x)
{ return x * x; } //OK, compile time evaluation only if x is a constant expression
const int res=square(5); //compile-time evaluation of square(5)
int y=getval();
int n=square(y);//dynamic evaluation of square(y)
The automatic defaulting to dynamic initialization lets you define a single constexpr function that accepts both constant expressions and non-constant expressions. You should also note that, unlike ordinary functions, you can’t call a constant expression function before it’s defined.
Constant Expression Data
A constant-expression value is a variable or data member declared constexpr. It must be initialized with a constant expression or an rvalue constructed by a constant expression constructor with constant expression arguments (I discuss constant expression constructors shortly). A constexpr value behaves as if it was declared const, except that it requires initialization before use and its initializer must be a constant expression. Consequently, a constexpr variable can always be used as part of another constant expression. For example:
struct S
{
private:
static constexpr int sz; // constexpr variable
public:
constexpr int two(); //constexpr function
};
constexpr int S::sz = 256;
enum DataPacket
{
Small=S::two(), //error. S::two() called before it was defined
Big=1024
};
constexpr int S::two() { return sz*2; }
constexptr S s;
int arr[s.two()]; //OK, s.two() called after its definition
Constant Expression Constructors
By default, an object with a nontrivial constructor undergoes dynamic initialization. However, under certain conditions, C++11 lets you declare a class’s constructor constexpr. A constexpr constructor allows the compiler to initialize the object at compile-time, provided that the constructor’s arguments are all constant expressions.
Formally, a constant expression constructor is one that meets the following criteria:
- It’s declared constexpr explicitly.
- It can have a member initialization list involving only potentially constant expressions (if the expressions used aren’t constant expressions then the initialization of that object will be dynamic).
- Its body must be empty.
An object of a user-defined type constructed with a constant expression constructor and constant expression arguments is called a user-defined literal. Consider the following class complex:
struct complex
{
//a constant expression constructor
constexpr complex(double r, double i) : re(r), im(i) { }//empty body
//constant expression functions
constexpr double real() { return re;}
constexpr double imag() { return im;}
private:
double re;
double im;
};
constexpr complex COMP(0.0, 1.0); // creates a literal complex
As you can see, a constant expression constructor is a private case of a constant expression function except that it doesn’t have a return value (because constructors don’t return values). Typically, the memory layout of COMP is similar to that of an array of two doubles. One of the advantages of user-defined literals with a small memory footprint is that an implementation can store them in the system’s ROM. Without a constexpr constructor, the object would require dynamic initialization and therefore wouldn’t be ROM-able.
As with ordinary constant expression functions, the constexpr constructor may accept arguments that aren’t constant expressions. In such cases, the initialization is dynamic:
double x = 1.0;
constexpr complex cx1(x, 0); // error: x isn’t a constant expression
const complex cx2(x, 1); //OK, dynamic initialization
constexpr double xx = COMP.real(); // OK
constexpr double imaglval=COMP.imag(); //OK, static init
complex cx3(2, 4.6); //dynamic initialization
Notice that if the initializer is a constant that isn’t declared constexpr, the implementation is free to choose between static and dynamic initialization. However, if the initializer is declared constexpr, the object undergoes static initialization.
In Conclusion
constexpr is an effective tool for ensuring compile-time evaluation of function calls, objects and variables. Compile-time evaluation of expressions often leads to more efficient code and enables the compiler to store the result in the system’s ROM. Additionally, constexpr can be used wherever a constant expression is required, such as the size of arrays and bit-fields, as an enumerator’s initializer, or in the forming of another constant expression. With respect to compiler support, GCC 4.6, Intel’s C++ 13, IBM’s XLC++ 12.1, and Clang 3.1 already support constexpr.
About the author:
Danny Kalev is a certified system analyst and software engineer specializing in C++. Kalev has written several C++ textbooks and contributes C++ content regularly on various software developers’ sites. He was a member of the C++ standards committee and has a master’s degree in general linguistics.
See also: