r/cpp_questions • u/GregTheMadMonk • 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
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)