Does static constexpr variable inside a function make sense?
Asked Answered
D

3

291

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?

Dieter answered 13/12, 2012 at 18:8 Comment(0)
C
360

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.

Conjuncture answered 13/12, 2012 at 20:12 Comment(18)
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.Wapentake
@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.)Conjuncture
In C++1y, contexpr functions will NOT be const.Acetum
@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.Conjuncture
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.Muriate
@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.Conjuncture
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.Muriate
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.Muriate
@Muriate 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.Densmore
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.Dieter
@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.Conjuncture
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: en.cppreference.com/w/cpp/language/constexpr (exactly one return statement.)Janeljanela
@andry: f is not declared constexpr in the OP.Conjuncture
@Conjuncture 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.Janeljanela
f is not declared static either. Perhaps I don't understand your point.Conjuncture
@Janeljanela 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.Swanherd
Array 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.Swanherd
@Conjuncture "you can't cast away const from a const object" Says who?Pernas
B
31

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.

Biddie answered 10/10, 2019 at 18:23 Comment(6)
can we not use constexpr const short constexpr_short for giving error if constexpr_short is initialized againMorry
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.Biddie
@Biddie 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?Tamica
@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: #30561604Biddie
Best answer, IMHO. ThanksModestia
@Biddie 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.Sapotaceous
N
27

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.

Nona answered 6/3, 2021 at 18:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.