Does static constexpr variable inside a function make sense?
Solution 1
The short answer is that not only is static
useful, it is pretty well always going to be desired.
First, note that static
and constexpr
are completely independent of each other. static
defines the object's lifetime during execution; constexpr
specifies that the object should be available during compilation. Compilation and execution are disjoint and discontiguous, both in time and space. So once the program is compiled, constexpr
is no longer relevant.
Every variable declared constexpr
is implicitly const
but const
and static
are almost orthogonal (except for the interaction with static const
integers.)
The C++
object model (§1.9) requires that all objects other than bit-fields occupy at least one byte of memory and have addresses; furthermore all such objects observable in a program at a given moment must have distinct addresses (paragraph 6). This does not quite require the compiler to create a new array on the stack for every invocation of a function with a local non-static const array, because the compiler could take refuge in the as-if
principle provided it can prove that no other such object can be observed.
That's not going to be easy to prove, unfortunately, unless the function is trivial (for example, it does not call any other function whose body is not visible within the translation unit) because arrays, more or less by definition, are addresses. So in most cases, the non-static const(expr)
array will have to be recreated on the stack at every invocation, which defeats the point of being able to compute it at compile time.
On the other hand, a local static const
object is shared by all observers, and furthermore may be initialized even if the function it is defined in is never called. So none of the above applies, and a compiler is free not only to generate only a single instance of it; it is free to generate a single instance of it in read-only storage.
So you should definitely use static constexpr
in your example.
However, there is one case where you wouldn't want to use static constexpr
. Unless a constexpr
declared object is either ODR-used or declared static
, the compiler is free to not include it at all. That's pretty useful, because it allows the use of compile-time temporary constexpr
arrays without polluting the compiled program with unnecessary bytes. In that case, you would clearly not want to use static
, since static
is likely to force the object to exist at runtime.
Solution 2
In addition to given answer, it's worth noting that compiler is not required to initialize constexpr
variable at compile time, knowing that the difference between constexpr
and static constexpr
is that to use static constexpr
you ensure the variable is initialized only once.
Following code demonstrates how constexpr
variable is initialized multiple times (with same value though), while static constexpr
is surely initialized only once.
In addition the code compares the advantage of constexpr
against const
in combination with static
.
#include <iostream>
#include <string>
#include <cassert>
#include <sstream>
const short const_short = 0;
constexpr short constexpr_short = 0;
// print only last 3 address value numbers
const short addr_offset = 3;
// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
// determine initial size of strings
std::string title = "value \\ address of ";
const size_t ref_size = ref_name.size();
const size_t title_size = title.size();
assert(title_size > ref_size);
// create title (resize)
title.append(ref_name);
title.append(" is ");
title.append(title_size - ref_size, ' ');
// extract last 'offset' values from address
std::stringstream addr;
addr << param;
const std::string addr_str = addr.str();
const size_t addr_size = addr_str.size();
assert(addr_size - offset > 0);
// print title / ref value / address at offset
std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}
// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
static short temp = const_short;
const short const_var = ++temp;
print_properties("const", &const_var, addr_offset);
if (counter)
const_value(counter - 1);
}
// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
static short temp = const_short;
static short static_var = ++temp;
print_properties("static", &static_var, addr_offset);
if (counter)
static_value(counter - 1);
}
// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
static short temp = const_short;
static const short static_var = ++temp;
print_properties("static const", &static_var, addr_offset);
if (counter)
static_const_value(counter - 1);
}
// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
constexpr short constexpr_var = constexpr_short;
print_properties("constexpr", &constexpr_var, addr_offset);
if (counter)
constexpr_value(counter - 1);
}
// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
static constexpr short static_constexpr_var = constexpr_short;
print_properties("static constexpr", &static_constexpr_var, addr_offset);
if (counter)
static_constexpr_value(counter - 1);
}
// final test call this method from main()
void test_static_const()
{
constexpr short counter = 2;
const_value(counter);
std::cout << std::endl;
static_value(counter);
std::cout << std::endl;
static_const_value(counter);
std::cout << std::endl;
constexpr_value(counter);
std::cout << std::endl;
static_constexpr_value(counter);
std::cout << std::endl;
}
Possible program output:
value \ address of const is 1 564
value \ address of const is 2 3D4
value \ address of const is 3 244
value \ address of static is 1 C58
value \ address of static is 1 C58
value \ address of static is 1 C58
value \ address of static const is 1 C64
value \ address of static const is 1 C64
value \ address of static const is 1 C64
value \ address of constexpr is 0 564
value \ address of constexpr is 0 3D4
value \ address of constexpr is 0 244
value \ address of static constexpr is 0 EA0
value \ address of static constexpr is 0 EA0
value \ address of static constexpr is 0 EA0
As you can see yourself constexpr
is initilized multiple times (address is not the same) while static
keyword ensures that initialization is performed only once.
Solution 3
Not making large arrays static
, even when they're constexpr
can have dramatic performance impact and can lead to many missed optimizations. It may slow down your code by orders of magnitude. Your variables are still local and the compiler may decide to initialize them at runtime instead of storing them as data in the executable.
Consider the following example:
template <int N>
void foo();
void bar(int n)
{
// array of four function pointers to void(void)
constexpr void(*table[])(void) {
&foo<0>,
&foo<1>,
&foo<2>,
&foo<3>
};
// look up function pointer and call it
table[n]();
}
You probably expect gcc-10 -O3
to compile bar()
to a jmp
to an address which it fetches from a table, but that is not what happens:
bar(int):
mov eax, OFFSET FLAT:_Z3fooILi0EEvv
movsx rdi, edi
movq xmm0, rax
mov eax, OFFSET FLAT:_Z3fooILi2EEvv
movhps xmm0, QWORD PTR .LC0[rip]
movaps XMMWORD PTR [rsp-40], xmm0
movq xmm0, rax
movhps xmm0, QWORD PTR .LC1[rip]
movaps XMMWORD PTR [rsp-24], xmm0
jmp [QWORD PTR [rsp-40+rdi*8]]
.LC0:
.quad void foo<1>()
.LC1:
.quad void foo<3>()
This is because GCC decides not to store table
in the executable's data section, but instead initializes a local variable with its contents every time the function runs. In fact, if we remove constexpr
here, the compiled binary is 100% identical.
This can easily be 10x slower than the following code:
template <int N>
void foo();
void bar(int n)
{
static constexpr void(*table[])(void) {
&foo<0>,
&foo<1>,
&foo<2>,
&foo<3>
};
table[n]();
}
Our only change is that we have made table
static
, but the impact is enormous:
bar(int):
movsx rdi, edi
jmp [QWORD PTR bar(int)::table[0+rdi*8]]
bar(int)::table:
.quad void foo<0>()
.quad void foo<1>()
.quad void foo<2>()
.quad void foo<3>()
In conclusion, never make your lookup tables local variables, even if they're constexpr
. Clang actually optimizes such lookup tables well, but other compilers don't. See Compiler Explorer for a live example.
Related videos on Youtube
David Stone
Member of C++ Standardization Committee, where I chair the Modules Study Group (SG2) and vice chair the Evolution Working Group (EWG).
Updated on July 24, 2021Comments
-
David Stone almost 3 years
If I have a variable inside a function (say, a large array), does it make sense to declare it both
static
andconstexpr
?constexpr
guarantees that the array is created at compile time, so would thestatic
be useless?void f() { static constexpr int x [] = { // a few thousand elements }; // do something with the array }
Is the
static
actually doing anything there in terms of generated code or semantics? -
Andrew Lazarus over 11 yearsI was thinking about this when I made the comment. ISTM
constexpr
, unlikeconst
, can always be put in read-only storage (you can't cast awayconstexpr
[?]) and that would obviate the need to push a fresh copy on the stack, even when it would be required forconst
variables. Sort of like pooling constant literal strings. But I don't know if that's legal. -
rici over 11 years@AndrewLazarus, you can't cast away
const
from aconst
object, only from aconst X*
which points to anX
. But that's not the point; the point is that automatic objects cannot have static addresses. As I said,constexpr
ceases to be meaningful once the compilation is finished, so there is nothing to cast away (and quite possibly nothing at all, because the object is not even guaranteed to exist at runtime.) -
kirbyfan64sos about 10 yearsIn C++1y, contexpr functions will NOT be const.
-
rici about 10 years@kirbyfan64sos: true, but (a) that's a different meaning of
const
and (b) it's constexpr member functions, and (c) it's irrelevant to this answer. However, I changedeverything
toevery variable
since I was just talking about variables anyway. -
void.pointer over 9 yearsI kind of feel like not only is this answer incredibly confusing but also self contradictory. For example you say that you almost always want
static
andconstexpr
but explain that they are orthogonal and independent, doing different things. You then mention a reason to NOT combine the two since it would ignore ODR-usage (which seems useful). Oh and I still don't see why static should be used with constexpr since static is for runtime stuff. You never explained why static with constexpr is important. -
rici over 9 years@void.pointer: You're right about the last paragraph. I changed the intro. I thought I had explained the importance of
static constexpr
(it prevents the constant array from having to be recreated on every function call), but I tweaked some words which might make it clearer. Thanks. -
void.pointer almost 9 yearsMight also be useful to mention compile time constants vs runtime constants. In other words, if a
constexpr
constant variable is only used in compile-time contexts and never needed at runtime, thenstatic
makes no sense, since by the point you get to the runtime, the value has been effectively "inlined". However, ifconstexpr
is used in runtime contexts (in other words, theconstexpr
would need to be converted toconst
implicitly, and available with a physical address for runtime code) it will wantstatic
to ensure ODR compliance, etc. That is my understanding, at least. -
void.pointer almost 9 yearsAn example for my last comment:
static constexpr int foo = 100;
. There is no reason why the compiler couldn't substitute usage offoo
everywhere for literal100
, unless code were doing something like&foo
. Sostatic
onfoo
has no usefulness in this case sincefoo
doesn't exist at runtime. Again all up to the compiler. -
underscore_d over 7 years@void.pointer Making a
static constexpr
ensures that it cannot occupy space within a class if it's a member variable. This is required (or just extremely desirable) if the variable is only really a private constant (e.g. if the class is a template). People previously used the 'enum
hack' for this, butconstexpr
and odr-used guarantees make that redundant and less powerful anyway. -
David Stone almost 7 yearsThinking about this answer more, it seems that the distinction is less important than advertised. You say that all distinct objects must have distinct addresses. The only way this is observable is if you pass the address of a constexpr local variable to some function, that function recursively calls the original function again, which passes the address of the "next" array again, and then you compare those two pointers for equality. That being said, if the compiler cannot see the body of a function that you pass a constexpr object to by reference or pointer, it must assume this might happen.
-
rici almost 7 years@DavidStone: Since the objects I'm talking about are things like lookup tables, which are of a non-trivial size, it is likely that they will be passed by reference, which is, in effect, an address. It is true that if the compiler can see the body of every function which uses the object, it could deduce that the address is never taken, but I honestly don't know whether compilers actually bother with this optimisation.
-
Andry over 5 yearsMight be important,
C++11
standard limited compiler would not compile// do something with the array
because brieflyconstexpr
function body inc++11
must consist of a single return statement returning non void value: en.cppreference.com/w/cpp/language/constexpr (exactly one return statement.
) -
rici over 5 years@andry:
f
is not declaredconstexpr
in the OP. -
Andry over 5 years@rici google brought me here by
static constexpr
in search field and a static function can return static variables with construction-on-call to prevent compromising of class/global scope statics construction order. -
rici over 5 years
f
is not declaredstatic
either. Perhaps I don't understand your point. -
akhileshzmishra about 4 yearscan we not use
constexpr const short constexpr_short
for giving error if constexpr_short is initialized again -
metablaster about 4 yearsyour syntax of
constexpr const
makes no sense becauseconstexpr
already isconst
, addingconst
once or multiple times is ignored by compiler. You are trying to catch an error but this isn't an error, that's how most compilers work. -
Thorbjørn Lindeijer about 3 years@metablaster Not sure about that, for example my compiler (GCC 10.2) warns about
constexpr char *sectionLabel = "Name"
due to the lack ofconst
, printing out "warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]". Or is this a faulty warning? -
metablaster about 3 years@ThorbjørnLindeijer Your compiler is correct, however it doesn't make my point wrong, because this applies only to
char
which is a special beast in C++. see this link why: stackoverflow.com/questions/30561104/… -
Ícaro Pires about 3 yearsBest answer, IMHO. Thanks
-
FrankHB over 2 years@Andry C++20's
constinit
is your friend. But anyway, it is not blessed in C++11 not just for your reason: it seems that no simple way can prevent a local staticconstexpr
object instantiated more than once by multiple instances of the enclosing member function template, while keeping the objectprivate
. -
FrankHB over 2 yearsArray subscription is often expected not ODR-used but impossible before C++17 (and the alternative, using an inline variable, is also not available before C++17 without implementation-specific extensions), so an out-of-class definition is required if it is declared in the class as a static data member. Finally I put the object and the subscription in a static member function returning the value of array element, and dropped the
constexpr
on the function when__cpp_constexpr >= 201304L
is not true.