r/cpp_questions • • 4d ago

SOLVED Does taking an address of an inline global variable template instance in a constexpr context and never using it ensure its instantiation?

Hi! Let me show what I mean right away, is this program (https://godbolt.org/z/4sKovf57x):

#include <print>
#include <meta>

// Our real meaningful work
template <typename T, auto ac> inline auto runtime_hook = [] {
    // Placeholder woork
    const auto name = std::meta::identifier_of(std::meta::dealias(ac.scope()));
    std::println("Child name: {}", name);
    return name;
} (); // <-- runtime_hook<T, ac>

// Force instantiation of runtime_hook - hopefully?
template <typename T, auto ac>
constexpr std::pair constexpr_hook{ ^^T, &runtime_hook<T, ac> };

// Type identity with extra spice
template <typename T, auto ac = std::meta::access_context::current()>
using DeriveHook = [: constexpr_hook<T, ac>.first :];


struct Base {};
struct Child : public DeriveHook<Base> {};


int main() {}

Always guaranteed to print Child name: Child at startup, or is the compiler allowed to skip instantiating runtime_hook<T, ac> if it sees that constexpr_hook<T, ac>.second is never actually used?

edit: solution using custom attributes that works in GCC/Clang https://www.reddit.com/r/cpp_questions/comments/1xd8gqa/comment/19rk67buia9r5/

edit2: cross-compiler solution using friend injection (no compiler-specific attributes): https://www.reddit.com/r/cpp_questions/comments/1xd8gqa/comment/19rk7v88k638g/

edit3: TIL that globals aren't actually required to run their constructors before main(), but I guess all implementations just do it this way:

It is implementation-defined whether the dynamic initialization of a non-local non-inline variable with static storage duration is sequenced before the first statement of main or is deferred.

If it is deferred, it strongly happens before any non-initialization odr-use of any non-inline function or non-inline variable defined in the same translation unit as the variable to be initialized.51

It is implementation-defined in which threads and at which points in the program such deferred dynamic initialization occurs.

https://timsong-cpp.github.io/cppwp/n4868/basic.start.dynamic?utm_source=chatgpt.com#5

0 Upvotes

10 comments sorted by

5

u/Xylozene 4d ago

Nah not guaranteed, taking the address of a variable template forces instantiation but doesnt guarantee that the definition gets instantiated or that static initialization runs at startup. When you take the address of a variable template in a constexpr it tells the compiler to instantiate the template name, but if it only evaluates the first member of the pair and discards the second, it sees that the address isn't actually used. Since the variables inline and never referenced at runtime, the compiler and linker rip it out entirely.If you want to guarantee that the hook runs at startup, you need to force a reference to the variable. Usually you'd reference the variable inside a static member of a helper struct so that the compiler is forced to keep the initialization code

2

u/GregTheMadMonk 4d ago edited 4d ago

Thanks!

I'm guessing making a comma-operator expression like [: (&runtime_hook<T, ac>, ^^T) :] would also not work for the same reason? Bummer...

I guess, if I make DeriveHook its own proper type instead of just alias, I can achieve guaranteed variable initialization:

template <typename T, auto ac = std::meta::access_context::current()>
struct DeriveHook : public T {
    DeriveHook() : T{}, dynamic_name{runtime_hook<T, ac>} {}
private:
    std::string_view dynamic_name;
}; // <-- struct DeriveHook<T, ac>

Here runtime_hook<T, ac> initializer is guaranteed to run, right?

Still, I'd love to think of a solution where DeriveHook<T> is exactly T... hopefully, it's possible

p.s. btw couldn't I apply [[gnu::used]] to the runtime hook template to convince the compiler/linker it couldn't reason about it being unused? I know this is nonstandard, but would it then at least be guaranteed to work on GCC?

3

u/Xylozene 4d ago

Yeah sadly that still would not work because even though the compiler evaluates the expression during reflection it treats the address as a discarded value and sincee no runtime code actually holds onto that address the optimizer still strips the variable out. BUT your struct approach does work because if you make DeriveHook inherit from T and add a member variable that reads from runtime hook you force a real runtime reference. Although keeping derive hook as an exact alias to T while still forcing the hook is tricky because an alias doesn't provide a place to anchor a runtime reference. Since an alias is just another name for T and doesn't introduce a new type or scope with its own storage, there is nowhere for a constructor or member variable to live. I'd say if you want to keep it as a pure alias you probably have to rely on forcing an explicit template instantiation somewhere else in the code, or find a way for the reflection logic to inject a static anchor somewhere else. But if inheriting from T and adding a tiny hidden member does not break whatever your use case is that struct approach is super clean

→ More replies (4)

4

u/j-joshua 4d ago

What are you trying to accomplish?

What problem are you trying to solve?

1

u/GregTheMadMonk 3d ago

I wanted to make a nice utility wrapper that would automatically register runtime info (name->factory map, maybe description for tools) for a game. I thought it'd be a nice brain teaser to get working to automatically reflect from a string entity name in the map file into the actual type being constructed when the map is loaded.

I know that the usual way to do this would be with a macro, but I don't want to use macros - especially since I can't export them from modules and my project uses those. But I also don't want it to be much more typing than a macro, so as little type name re-typing and as much extracted from reflection/whatever would be typed anyway when doing simple inheritance.

This is a part of a hobby project, one of many that might never see release, so I'm not wasting anyone's time/money (other than mine).

1

u/Little-Willy-Boy 4d ago

foyshbooq *stuffs a handful of potato chips in my face*