Difference between `constexpr` and `const`
What's the difference between constexpr
and const
?
- When can I use only one of them?
- When can I use both and how should I choose one?
Solution 1:
Basic meaning and syntax
Both keywords can be used in the declaration of objects as well as functions. The basic difference when applied to objects is this:
-
const
declares an object as constant. This implies a guarantee that once initialized, the value of that object won't change, and the compiler can make use of this fact for optimizations. It also helps prevent the programmer from writing code that modifies objects that were not meant to be modified after initialization. -
constexpr
declares an object as fit for use in what the Standard calls constant expressions. But note thatconstexpr
is not the only way to do this.
When applied to functions the basic difference is this:
-
const
can only be used for non-static member functions, not functions in general. It gives a guarantee that the member function does not modify any of the non-static data members (except for mutable data members, which can be modified anyway). -
constexpr
can be used with both member and non-member functions, as well as constructors. It declares the function fit for use in constant expressions. The compiler will only accept it if the function meets certain criteria (7.1.5/3,4), most importantly (†):- The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single
return
statement is allowed. In the case of a constructor, only an initialization list, typedefs, and static assert are allowed. (= default
and= delete
are allowed, too, though.) - As of C++14, the rules are more relaxed, what is allowed since then inside a constexpr function:
asm
declaration, agoto
statement, a statement with a label other thancase
anddefault
, try-block, the definition of a variable of non-literal type, definition of a variable of static or thread storage duration, the definition of a variable for which no initialization is performed. - The arguments and the return type must be literal types (i.e., generally speaking, very simple types, typically scalars or aggregates)
- The function body must be non-virtual and extremely simple: Apart from typedefs and static asserts, only a single
Constant expressions
As said above, constexpr
declares both objects as well as functions as fit for use in constant expressions. A constant expression is more than merely constant:
-
It can be used in places that require compile-time evaluation, for example, template parameters and array-size specifiers:
template<int N> class fixed_size_list { /*...*/ }; fixed_size_list<X> mylist; // X must be an integer constant expression int numbers[X]; // X must be an integer constant expression
-
But note:
-
Declaring something as
constexpr
does not necessarily guarantee that it will be evaluated at compile time. It can be used for such, but it can be used in other places that are evaluated at run-time, as well. -
An object may be fit for use in constant expressions without being declared
constexpr
. Example:int main() { const int N = 3; int numbers[N] = {1, 2, 3}; // N is constant expression }
This is possible because
N
, being constant and initialized at declaration time with a literal, satisfies the criteria for a constant expression, even if it isn't declaredconstexpr
.
So when do I actually have to use constexpr
?
- An object like
N
above can be used as constant expression without being declaredconstexpr
. This is true for all objects that are: const
- of integral or enumeration type and
- initialized at declaration time with an expression that is itself a constant expression
[This is due to §5.19/2: A constant expression must not include a subexpression that involves "an lvalue-to-rvalue modification unless […] a glvalue of integral or enumeration type […]" Thanks to Richard Smith for correcting my earlier claim that this was true for all literal types.]
-
For a function to be fit for use in constant expressions, it must be explicitly declared
constexpr
; it is not sufficient for it merely to satisfy the criteria for constant-expression functions. Example:template<int N> class list { }; constexpr int sqr1(int arg) { return arg * arg; } int sqr2(int arg) { return arg * arg; } int main() { const int X = 2; list<sqr1(X)> mylist1; // OK: sqr1 is constexpr list<sqr2(X)> mylist2; // wrong: sqr2 is not constexpr }
When can I / should I use both, const
and constexpr
together?
A. In object declarations. This is never necessary when both keywords refer to the same object to be declared. constexpr
implies const
.
constexpr const int N = 5;
is the same as
constexpr int N = 5;
However, note that there may be situations when the keywords each refer to different parts of the declaration:
static constexpr int N = 3;
int main()
{
constexpr const int *NP = &N;
}
Here, NP
is declared as an address constant-expression, i.e. a pointer that is itself a constant expression. (This is possible when the address is generated by applying the address operator to a static/global constant expression.) Here, both constexpr
and const
are required: constexpr
always refers to the expression being declared (here NP
), while const
refers to int
(it declares a pointer-to-const). Removing the const
would render the expression illegal (because (a) a pointer to a non-const object cannot be a constant expression, and (b) &N
is in-fact a pointer-to-constant).
B. In member function declarations. In C++11, constexpr
implies const
, while in C++14 and C++17 that is not the case. A member function declared under C++11 as
constexpr void f();
needs to be declared as
constexpr void f() const;
under C++14 in order to still be usable as a const
function.
Solution 2:
const
applies for variables, and prevents them from being modified in your code.
constexpr
tells the compiler that this expression results in a compile time constant value, so it can be used in places like array lengths, assigning to const
variables, etc. The link given by Oli has a lot of excellent examples.
Basically they are 2 different concepts altogether, and can (and should) be used together.
Solution 3:
Overview
-
const
guarantees that a program does not change an object’s value. However,const
does not guarantee which type of initialization the object undergoes.Consider:
const int mx = numeric_limits<int>::max(); // OK: runtime initialization
The function
max()
merely returns a literal value. However, because the initializer is a function call,mx
undergoes runtime initialization. Therefore, you cannot use it as a constant expression:int arr[mx]; // error: “constant expression required”
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. It 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.
Constant-expression functions
A constant-expression function is a function declared constexpr
. Its body must be non-virtual and consist of a single return statement only, apart from typedefs and static asserts. Its arguments and return value must have literal types. It can be used with non-constant-expression arguments, but when that is done the result is not a constant expression.
A constant-expression function is meant to replace macros and hardcoded literals without sacrificing performance or type safety.
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 is not just a return statement
constexpr int square(int x)
{ return x * x; } // OK: compile-time evaluation only if x is a constant expression
const int res = square(5); // OK: compile-time evaluation of square(5)
int y = getval();
int n = square(y); // OK: runtime evaluation of square(y)
Constant-expression objects
A constant-expression object is an object declared constexpr
. It must be initialized with a constant expression or an rvalue constructed by a constant-expression constructor with constant-expression arguments.
A constant-expression object behaves as if it was declared const
, except that it requires initialization before use and its initializer must be a constant expression. Consequently, a constant-expression object can always be used as part of another constant expression.
struct S
{
constexpr int two(); // constant-expression function
private:
static constexpr int sz; // constant-expression object
};
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; }
constexpr S s;
int arr[s.two()]; // OK: s.two() called after its definition
Constant-expression constructors
A constant-expression constructor is a constructor declared constexpr
. It can have a member initialization list but its body must be empty, apart from typedefs and static asserts. Its arguments must have literal types.
A constant-expression constructor allows the compiler to initialize the object at compile-time, provided that the constructor’s arguments are all constant expressions.
struct complex
{
// constant-expression constructor
constexpr complex(double r, double i) : re(r), im(i) { } // OK: 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
double x = 1.0;
constexpr complex cx1(x, 0); // error: x is not a constant expression
const complex cx2(x, 1); // OK: runtime initialization
constexpr double xx = COMP.real(); // OK: compile-time initialization
constexpr double imaglval = COMP.imag(); // OK: compile-time initialization
complex cx3(2, 4.6); // OK: runtime initialization
Tips from the book Effective Modern C++ by Scott Meyers about constexpr
:
-
constexpr
objects are const and are initialized with values known during compilation; -
constexpr
functions produce compile-time results when called with arguments whose values are known during compilation; -
constexpr
objects and functions may be used in a wider range of contexts than non-constexpr
objects and functions; -
constexpr
is part of an object’s or function’s interface.
Source: Using constexpr to Improve Security, Performance and Encapsulation in C++.