You can do it, you just need to replace constexpr with const in the header:
// declaration, possibly in header
extern const int i;
// source file
constexpr int i = 0;
The basic idea behind constexpr for objects is:
- make it const
- initialize it with a constant expression
The former part can be done by using const in the header, latter part is only relevant to the initialization in the source file.
Is this really allowed?!
Yes. Let's look at the relevant sections in the standard:
Two declarations of entities declare the same entity if, [...], they correspond, have the same target scope that is not a function or template parameter scope, and either
- they appear in the same translation unit, or [...]
- they both declare names with external linkage.
- [basic.link] §8
In plain terms, both i have the same name so they correspond, and they both have external linkage due to extern.
For any two declarations of an entity E:
- If one declares Eto be a variable or function, the other shall declareEas one of the same type.
- [...]
- [basic.link] §11
That begs the question: are two variables the same type if one is constexpr and the other is const?
A constexpr specifier used in an object declaration declares the object as const. [...]
- [dcl.constexpr] §6
The answer is yes, it just makes our object const, it doesn't change the type in any other way. The only remaining question is whether we are allowed to put constexpr on one declaration but not on another:
If any declaration of a function or function template has a constexpr or consteval specifier, then all its declarations shall contain the same specifier.
- [dcl.constexpr] §1
No, there are only restrictions for functions, not for variables. It is allowed to make one declaration const and the other constexpr.
It is allowed, but isn't it totally useless?
Not entirely. One possible use-case is if you have a large constexpr lookup table that you want to compute at compile time, but don't want to put this initialization into a header, in order to reduce compile times.
If it's really important to compute this table at compile time, but not so important to have a definition of its contents visible everywhere (for inlining and optimizations), extern constexpr can help.
- inline constexprwould require an identical definition (and initialization) everywhere to not violate the one definition rule. As a result, we incur the cost in every translation unit that includes our header.
- static constexpris even worse, because every translation unit has its own copy of this large lookup table.
- extern constexprcovers this use-case perfectly.
Note: not all compilers conform to the standard by default. Use /Zc:externConstexpr when compiling with MSVC.