Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

constexpr: Generalized Constant Expressions

Last updated Jan 1, 2003.

The C++98 rules regarding constant expressions are too restrictive. They force implementers to use low-tech macros instead of inline functions to ensure the compile-time evaluation of constant expressions. This issue as well as other limitations of C++98 constant expression rules have led to formation of a generalized constant expression mechanism for C++09.

The Problem with Functions In Constant Expressions

The std::numeric_limits class template is meant to replace C’s <limits.h>. For example, the expression

numeric_limits<int>::max() 

is functionally equivalent to the macro INT_MAX. max() is in fact an inline static function that returns a constant value:

static inline int numeric_limits<int>::max () { return INT_MAX; }

However, there is a crucial difference between the following two expressions:

const int x=INT_MAX; 
const int y=numeric_limits<int>::max() ;

x is an integral constant whereas the numeric_limits<int>::max() function call isn’t. Although both statements will compile, y cannot appear in a context that requires a constant expression (although it’s declared const). Consider:

int arr[x]; //fine
int arr2[y]; //error, "constant expression required"

Seemingly, there should be no difference between x and y in this respect since numeric_limits::max() is inlined. The compiler is therefore able to "see through" the max() function call, treating the initializations of both x and y in the same manner. However, due to an unnecessarily restrictive notion of constant expressions in C++98, a function call, when used as an initializer, is considered a dynamic initializer, regardless of whether the call is inlined. This urges users to prefer macros when values need to be known at compile time.

Surprising Dynamic Initialization

A similar problem is witnessed with the initialization of const static data members. Consider:

struct V {
 static const int g;
};
const int a = 10 * V::g; // dynamic initialization
const int V::g = 98; //initialization comes too late

Seemingly, a can be computed at compile time because the value V::g is known at compile time. However, the initialization of V::g appears too late to allow the compile time evaluation of a. Consequently, a is initialized dynamically. If you place the initializer of V::g before the definition of a, a becomes a constant expression:

const int V::g = 98; //compile time initialization
const int a = 10 * V::g; //compile time initialization

There are at least two problems with this state of affairs:

  • There’s no syntactic clue to suggest that a’s initialization is deferred to runtime.
  • Because of its dynamic initialization, a cannot be used in a context that requires a constant expression.

The generalized constant expressions proposal<> by Reis, Stroustrup and Maurer addresses these problems.

Constant Expression Functions

The authors propose a new construct called constant expression functions. A constant expression function is a function that can be "executed" at compile time by means of inlining and evaluating the result at compile-time. A function is a constant-expression function if it meets the following criteria:

  • It returns a value (i.e., it’s not a void function)
  • The body of the function consists of a single return statement of the form:
return expr;

  • The function is declared as constexpr

The expression expr in the return statement must evaluate to a constant expression after substitution. Additionally, a constant expression function shall not be called before it has been defined. Here are a few examples:

constexpr int f(int x)
{ return x * x; } // OK

constexpr long long_max()
{ return 2147483647; } // OK

constexpr int abs(int n)
{ return x < 0 ? -n : n; } // OK

constexpr void g(int n) // error: void function
{ return; }

constexpr int next(int x)
{ return ++x; } // error: ++ not allowed in a constant expression

constexpr int g(int x) // error: body contains more than return expr;
{int n = x;
while (--x > 1) n *= x;
return n;
}
constexpr int quadruple(int y);
enum { size = quadruple(16) }; // error: quadruple() isn’t defined yet

template<typename T>

constexpr int getsizeof(T t)
{ return sizeof (t); } // OK

The comment "OK" indicates that the function body can be evaluated as a constant expression given constant expression argument(s).

Constant-expression data

The constexpr specifier may apply to variables and data members. A variable or data member declared with the constexpr specifier is called a constant-expression value. It must be initialized with a constant expression. For example:

const double m = 10;
constexpr double e = m * quadruple(9.6); // OK
extern const int s;
constexpr int a = quadruple(s); // error: quadruple(s) is not a constant expression

A variable or data member declared with constexpr behaves as if it were declared const. However, unlike a const variable or data member, it requires initialization before it’s used and its initializer must be a constant-expression. These requirements ensure that a constexpr variable can always be used as a constant expression, e.g., in an array definition.