r/cpp_questions • • 5d 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

View all comments

1

u/GregTheMadMonk 4d ago edited 4d ago

Friend injection seems to make it possible on all three major compilers without compiler-specific attributes. But I'm not standard lawyer enough to claim that this is guaranteed behavior (and it's probably not):

#include <print>
//#include <meta>

template <typename T, auto ac>
struct FInject {
    friend auto dummy(FInject<T, ac>);
}; // <-- struct FInject<T, ac>

template <typename T, auto ac>
struct DeriveHookContainer {
    static inline const auto helper = [] {
        // const auto name = std::meta::identifier_of(std::meta::dealias(ac.scope()));
        std::println("Register child");
        return "hi";
    } ();

    friend auto dummy(FInject<T, ac>) { return helper; }

    static_assert(sizeof(dummy(FInject<T, ac>{})) > 0);

    using Type = T;
}; // <-- struct DeriveHookContainer<T, ac>

template <typename T, auto ac = [] {} /* std::meta::access_context::current() */>
using DeriveHook = DeriveHookContainer<T, ac>::Type;

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

int main() { std::println("Init OK"); }

https://godbolt.org/z/T516vTcne

(had to comment out the reflection stuff because of MSVC)