I believe there is a difference. Let's rename them so that we can talk about them more easily:
const double PI1 = 3.141592653589793;
constexpr double PI2 = 3.141592653589793;
Both PI1
and PI2
are constant, meaning you can not modify them. However only PI2
is a compile-time constant. It shall be initialized at compile time. PI1
may be initialized at compile time or run time. Furthermore, only PI2
can be used in a context that requires a compile-time constant. For example:
constexpr double PI3 = PI1; // error
but:
constexpr double PI3 = PI2; // ok
and:
static_assert(PI1 == 3.141592653589793, ""); // error
but:
static_assert(PI2 == 3.141592653589793, ""); // ok
As to which you should use? Use whichever meets your needs. Do you want to ensure that you have a compile time constant that can be used in contexts where a compile-time constant is required? Do you want to be able to initialize it with a computation done at run time? Etc.
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 that constexpr
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, a goto
statement, a statement with a label other than case
and default
, 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)
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 declared constexpr
.
So when do I actually have to use constexpr
?
- An object like
N
above can be used as constant expression without being declared constexpr
. This is true for all objects that are:
const
and
- 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.
Best Answer
I think your statement saying that
const
andconstexpr
"are semantically identical" should be revised: they both declare objects whose value cannot change, butconstexpr
also requires the initializer expression to be computable at compile-time.Now if the expression on the right-hand side cannot be computed at compile-time, using
constexpr
is out of the question. On the other hand, as long as the initializer is a literal, you could useconstexpr
, but keep into account what the semantics of your variable is: does your constant variable really represent something whose value should be computable at compile-time?In a SW maintenance/evolution optics, it is possible that you will change the way you initialize your variable throughout time: today the initializer is a literal, tomorrow it might be a more complex expression.
Regardless of the way you are assigning it a value now, do you think that
i
will ever need to be initialized by anything else than a literal, and that the initializing expression may not be computable at compile-time? If that is the case, then just make your variableconst
, even though you are currently initializing it with a literal; otherwise, make itconstexpr
.In other words, choose the qualifier that best expresses the semantics of your variable.