Search code examples
c++entity-component-system

Why is it possible to modify a static const variable that is declared inside a function?


I'm implementing an ECS framework for my game engine and investigating ways to identify component types at runtime. This means I can dynamically group components of a similar type contiguously in memory. For example, I can have two separate arrays for Position and Velocity components that my systems can loop over.

I currently use typeid(), but I came across an article that uses static const variables to generate unique IDs for each type. The general idea can be summarized in the code snippet below:

#include <iostream>

struct Position { float x, y; };
struct Velocity { float dx, dy; };

template <typename T>
int test(int x) {
    static const int y = x;
    return y;
}

int main(int argc, char **argv) {
    std::cout << test<Position>(1) << " ";
    std::cout << test<Position>(2) << " ";
    std::cout << test<Velocity>(3) << " ";
    return 0;
}

This outputs 1 1 3, but I would expect that since it's attempting to modify a constant (specifically at the second call), it would fail to compile. Why does this work?


Solution

  • Static variables are only initialized once.

    The second (and consequent) time the initialization is skipped.

    Here also you have 2 different functions test. 1 for each unique template parameter type.

    An example, for a simple function call of Your code:

    auto f() { return test<Position>(1); }
    

    This will result in the following assembler code, where You can see that there is a check to verify if the variable has been already initialized or return the already set value. If it has not been set, then it will be set in a thread-safe manner.

    f():
            movzx   eax, BYTE PTR guard variable for int test<Position>(int)::y[rip]
            test    al, al   // <----- check if intialized
            je      .L13     // <----- if not initialized go to .L13
                             // otherwise return the value
            mov     eax, DWORD PTR int test<Position>(int)::y[rip]
            ret
    .L13:
            sub     rsp, 8
            mov     edi, OFFSET FLAT:guard variable for int test<Position>(int)::y
            call    __cxa_guard_acquire
            test    eax, eax
            jne     .L14
            mov     eax, DWORD PTR int test<Position>(int)::y[rip]
            add     rsp, 8
            ret
    .L14:
            mov     DWORD PTR int test<Position>(int)::y[rip], 1
            mov     edi, OFFSET FLAT:guard variable for int test<Position>(int)::y
            call    __cxa_guard_release
            mov     eax, DWORD PTR int test<Position>(int)::y[rip]
            add     rsp, 8
            ret