281

If I have a variable inside a function (say, a large array), does it make sense to declare it both static and constexpr? constexpr guarantees that the array is created at compile time, so would the static 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?

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
David Stone
  • 26,872
  • 14
  • 68
  • 84

3 Answers3

347

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.

user5534993
  • 518
  • 2
  • 17
rici
  • 234,347
  • 28
  • 237
  • 341
  • 1
    I was thinking about this when I made the comment. ISTM `constexpr`, unlike `const`, can always be put in read-only storage (you can't cast away `constexpr`[?]) and that would obviate the need to push a fresh copy on the stack, even when it would be required for `const` variables. Sort of like pooling constant literal strings. But I don't know if that's legal. – Andrew Lazarus Dec 13 '12 at 22:56
  • 2
    @AndrewLazarus, you can't cast away `const` from a `const` object, only from a `const X*` which points to an `X`. 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.) – rici Dec 13 '12 at 23:54
  • In C++1y, contexpr functions will NOT be const. – kirbyfan64sos Feb 24 '14 at 20:28
  • 1
    @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 changed `everything` to `every variable` since I was just talking about variables anyway. – rici Feb 24 '14 at 22:39
  • 42
    I 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` and `constexpr` 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. – void.pointer Oct 01 '14 at 22:08
  • 3
    @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. – rici Oct 02 '14 at 03:31
  • 15
    Might 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, then `static` makes no sense, since by the point you get to the runtime, the value has been effectively "inlined". However, if `constexpr` is used in runtime contexts (in other words, the `constexpr` would need to be converted to `const` implicitly, and available with a physical address for runtime code) it will want `static` to ensure ODR compliance, etc. That is my understanding, at least. – void.pointer Jun 11 '15 at 17:54
  • 8
    An example for my last comment: `static constexpr int foo = 100;`. There is no reason why the compiler couldn't substitute usage of `foo` everywhere for literal `100`, unless code were doing something like `&foo`. So `static` on `foo` has no usefulness in this case since `foo` doesn't exist at runtime. Again all up to the compiler. – void.pointer Jun 11 '15 at 17:56
  • @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, but `constexpr` and odr-used guarantees make that redundant and less powerful anyway. – underscore_d Sep 07 '16 at 14:28
  • Thinking 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. – David Stone Jun 12 '17 at 01:50
  • 1
    @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. – rici Jun 12 '17 at 02:00
  • 1
    Might be important, `C++11` standard limited compiler would not compile `// do something with the array` because briefly `constexpr` function body in `c++11` must consist of a single return statement returning non void value: https://en.cppreference.com/w/cpp/language/constexpr (`exactly one return statement.`) – Andry Nov 27 '18 at 18:48
  • @andry: `f` is not declared `constexpr` in the OP. – rici Nov 28 '18 at 02:05
  • @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. – Andry Nov 28 '18 at 03:21
  • `f` is not declared `static` either. Perhaps I don't understand your point. – rici Nov 28 '18 at 03:29
  • @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 static `constexpr` object instantiated more than once by multiple instances of the enclosing member function template, while keeping the object `private`. – FrankHB Nov 20 '21 at 23:11
  • Array subscription is often expected not ODR-used but [impossible before C++17](https://stackoverflow.com/a/28446388) (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. – FrankHB Nov 20 '21 at 23:13
  • @rici "_you can't cast away const from a const object_" Says who? – curiousguy Jun 02 '23 at 21:27
27

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.

metablaster
  • 1,958
  • 12
  • 26
  • can we not use `constexpr const short constexpr_short` for giving error if **constexpr_short** is initialized again – akhileshzmishra Feb 16 '20 at 07:47
  • your syntax of `constexpr const` makes no sense because `constexpr` already is `const`, adding `const` 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. – metablaster Feb 16 '20 at 14:30
  • @metablaster Not sure about that, for example my compiler (GCC 10.2) warns about `constexpr char *sectionLabel = "Name"` due to the lack of `const`, printing out "warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]". Or is this a faulty warning? – Thorbjørn Lindeijer Feb 25 '21 at 11:56
  • 1
    @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: https://stackoverflow.com/questions/30561104/const-constexpr-char-vs-constexpr-char – metablaster Mar 21 '21 at 02:52
  • Best answer, IMHO. Thanks – Ícaro Pires Mar 29 '21 at 21:53
  • 5
    @metablaster It's not that `char` is a "special beast". `constexpr char*` simply applies `const` to the pointer itself and not to the pointer type. It's `char * const` versus `const char*`. This syntactical difference applies to any pointer type. – Matthew M. Aug 15 '22 at 13:37
26

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.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96