What\'s the difference between constexpr
and const
?
I don't think any of the answers really make it clear exactly what side effects it has, or indeed, what it is.
constexpr
and const
at namespace/file-scope are identical when initialised with a literal or expression; but with a function, const
can be initialised by any function, but constexpr
initialised by a non-constexpr (a function that isn't marked with constexpr or a non constexpr expression) will generate a compiler error. Both constexpr
and const
are implicitly internal linkage for variables (well actually, they don't survive to get to the linking stage if compiling -O1 and stronger, and static
doesn't force the compiler to emit an internal (local) linker symbol for const
or constexpr
when at -O1 or stronger; the only time it does this is if you take the address of the variable. const
and constexpr
will be an internal symbol unless expressed with extern
i.e. extern constexpr/const int i = 3;
needs to be used). On a function, constexpr
makes the function permanently never reach the linking stage (regardless of extern
or inline
in the definition or -O0 or -Ofast), whereas const
never does, and static
and inline
only have this effect on -O1 and above. When a const
/constexpr
variable is initialised by a constexpr
function, the load is always optimised out with any optimisation flag, but it is never optimised out if the function is only static
or inline
, or if the variable is not a const
/constexpr
.
Standard compilation (-O0)
#include
constexpr int multiply (int x, int y)
{
return x * y;
}
extern const int val = multiply(10,10);
int main () {
std::cout << val;
}
compiles to
val:
.long 100 //extra external definition supplied due to extern
main:
push rbp
mov rbp, rsp
mov esi, 100 //substituted in as an immediate
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream >::operator<<(int)
mov eax, 0
pop rbp
ret
__static_initialization_and_destruction_0(int, int):
.
.
.
However
#include
const int multiply (int x, int y)
{
return x * y;
}
const int val = multiply(10,10); //constexpr is an error
int main () {
std::cout << val;
}
Compiles to
multiply(int, int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
mov eax, DWORD PTR [rbp-4]
imul eax, DWORD PTR [rbp-8]
pop rbp
ret
main:
push rbp
mov rbp, rsp
mov eax, DWORD PTR val[rip]
mov esi, eax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream >::operator<<(int)
mov eax, 0
pop rbp
ret
__static_initialization_and_destruction_0(int, int):
.
.
.
mov esi, 10
mov edi, 10
call multiply(int, int)
mov DWORD PTR val[rip], eax
This clearly shows that constexpr
causes the initialisation of the const/constexpr
file-scope variable to occur at compile time and produce no global symbol, whereas not using it causes initialisation to occur before main
at runtime.
Compiling using -Ofast
Even -Ofast doesn't optimise out the load! https://godbolt.org/z/r-mhif, so you need constexpr
constexpr
functions can also be called from inside other constexpr
functions for the same result. constexpr
on a function also prevents use of anything that can't be done at compile time in the function; for instance, a call to the <<
operator on std::cout
.
constexpr
at block scope behaves the same in that it produces an error if initialised by a non-constexpr function; the value is also substituted in immediately.
In the end, its main purpose is like C's inline function, but it is only effective when the function is used to initialise file-scope variables (which functions cannot do on C, but they can on C++ because it allows dynamic initialisation of file-scope variables), except the function cannot export a global/local symbol to the linker as well, even using extern/static
, which you could with inline
on C; block-scope variable assignment functions can be inlined simply using an -O1 optimisation without constexpr
on C and C++.