You cannot directly static-initialize a class without naming the instantiation:
struct some_class;
// ... some_class defined somewhere else ...
static some_class; // Invalid
static some_class{}; // Invalid
inline static some_class; // Invalid
inline static some_class{}; // Invalid
And you also cannot make a class instantiate itself:
struct some_class {
inline static some_class s_instance{}; // Invalid; some_class is not a complete type
}
There are many other ways to static initialize a class though, often used as part of a self-registration pattern. You can concatenate the class name with _instance
for example, or the line number, or specialize a separate template class, or whatever. These patterns work, and there aren't serious problems with them. Sure, you're relying on preprocessor string concatenation, or you're relying that nowhere else do you need to reuse the name. The biggest inconvenience is it polluting auto-completion / intellisense.
And you can't just make a static_initializer class to instantiate it, because then you still have to instantiate the template class and use it in some way somewhere else in your code. Which just puts you back at square one... or does it?
There's one way you can instantiate that template class without referencing it anywhere in runtime code: using it in a static_assert.
namespace static_initializer {
template<typename T>
struct instance_container {
static constexpr T* instance() {
return &s_instance;
}
inline static T s_instance{};
};
} // namespace static_initializer
// ... Some .cpp file
struct some_class {
some_class() {
std::cout << "test text" << std::endl;
}
};
static_assert(static_initializer::instance_container<some_class>::instance() != nullptr);
// Neither some_class nor instance_container<some_class> are referenced anywhere else
Since the static_assert has to resolve instance_container<some_class>
, it should instantiate all static members of that class. Even though the value for instance() is thrown away, and indeed instance() itself could very well not make it to the end binary, the static_assert is still asserting that instance() would return a valid pointer if called. The compiler cannot safely optimize out instance_container<some_class>::s_instance
here. Since this isn't a static function variable, it will be initialized at static initialization rather than be delayed.
This works on GCC >= 8.4 | >= 9.3 and Clang >= 5.0 with -std=c++17
, as well as MSVC >= 19.14 with /std:c++17
. Macro helpers are provided via [static_initializer.hpp] and [static_initializer_multi.hpp].
Caveats:
- Everything that comes with using an
inline static
variable. - The instance can still be referenced by code, albeit through static_initializer::instance(). If that's a deal-breaker for whatever reason, move s_instance to private and bit-or s_instance with some garbage. Then it'll be truly impossible to reference by name or instance_container. That does not mean it's truly impossible to reference.
- The syntax still isn't as pretty as desired.
- If a class needs to be static initialized more than once for whatever reason, you have to incorporate
__FILE__
and__LINE__
magic this is supposed to avoid, or some other magic/workaround. - It's non-trivial to pass arguments to the constructor. Either template parameters must be used, or
instance_container
must be specialized. A macro could provide that specialization, but namespacing has to be kept in mind, and most of the point of this exercise is to give a feasible no-macro static initializer.
Key benefits:
- It's a self-contained self-registration pattern with no chance for naming collisions, so long as a class only need be instantiated once (i.e: singletons).
- No need for macros, for default-constructed singletons.
- Code auto-completion isn't polluted
Other notes: What I was originally looking for was a way to abuse static_assert to test if arbitrary code compiles or not, without naming a function. That can be done as below:
#define ASSERT_COMPILES(expr) static_assert([]() { expr; } != nullptr, "Your compiler is whack yo");
#define ASSERT_COMPILES_CONSTEXPR(expr) static_assert([]() constexpr { expr; } != nullptr, "Your compiler is whack yo");