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?
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