1
\$\begingroup\$

I'm trying to reduce the boilerplate of a lot of header-only classes I'm using. Each of these classes must go through a registration step. I want this step to be defined in the same file as the class definition.

I came to the following solution (-std=c++17 required):

#include <iostream>
#include <typeinfo>

template<typename T>
bool __autoRegisteringFunction()  {
    std::cout << "Registering: " << typeid(T).name() << std::endl;
    return true;
}

#define REGISTER_CLASS(TYPE) \
    inline static const bool __registered = __autoRegisteringFunction<TYPE>();

class Foo {
    REGISTER_CLASS(Foo)
};

class Bar {
    REGISTER_CLASS(Bar)
};

int main() {
    return 0;
}

While doing some research, I realized it was even possible to skip the class name in the macro by being clever (inspired from this library):

#include <iostream>
#include <typeinfo>

template<typename T>
bool __autoRegisteringFunction()  {
    std::cout << "Registering: " << typeid(T).name() << std::endl;
    return true;
}

template<typename T>
struct __AutoRegisteringClass {
    inline static const bool __registered = __autoRegisteringFunction<T>();
};

#define REGISTER_CLASS() \
    const void* __autoRegisteringMethod() const { \
        return &__AutoRegisteringClass<decltype(*this)>::registered; \
    }

class Foo {
    REGISTER_CLASS()
};

class Bar {
    REGISTER_CLASS()
};

int main() {
    return 0;
}

Note that I simplified the example. The macro I plan to use needs more parameters, not requiring to pass the class name would both avoid possible typos and reduce repeatability. That's why I'm interested in the 2nd solution.

From what I tested it works quite well. I still have a few questions:

  • Is the first solution safe from the "static initialization order fiasco"?
  • Is using a dummy method (never called) for instantiating a template thanks to decltype(*this) a common practice (or a known trick) in C++ meta-programming?
  • Would you consider the given implementations well defined by standards and thus globally safe to use?
  • Could the pattern I'm willing to use be somehow simplified?
\$\endgroup\$
3
  • \$\begingroup\$ Please post the real deal instead of the simplified example, as indicated in the help center. \$\endgroup\$
    – Mast
    Dec 14, 2021 at 20:16
  • \$\begingroup\$ @Mast My concerns are solely about the self-registration pattern I shared. I understand that context can be important, but in this case it would complicate the code unnecessarily. A more complete implementation can be found in the link to the library I shared. \$\endgroup\$
    – Delgan
    Dec 14, 2021 at 20:23
  • \$\begingroup\$ Note that there is a tiny bit of overhead for inline static member variables at construction time. \$\endgroup\$
    – G. Sliepen
    Dec 15, 2021 at 17:43

2 Answers 2

2
\$\begingroup\$

Questions:

Is the first solution safe from the "static initialization order fiasco"?

I hate that term.
It is not relevant as there are no ordering issues in the code.

The "static initialization order problem" happens when a static storage duration object depends on another static storage duration object that is in another compilation unit (and thus it is hard to guarantee the order of initialization without knowing things about the compiler and thus things are not portable easily).

Note: There are is a simple solution to this problem once you know it exists.

Is using a dummy method (never called) for instantiating a template thanks to decltype(*this) a common practice (or a known trick) in C++ meta-programming?

Yes.

Would you consider the given implementations well defined by standards and thus globally safe to use?

Very close:

  • The double underscore breaks some rules.
    But can be fixed.
  • Not sure if the static inline const is guaranteed to not require declaration, I think that is an optimization.
    But can be fixed.

Could the pattern I'm willing to use be somehow simplified?

There is always somebody out there that will can come up with something better.

Code Review:

This function should be inline (it is obviously in a header file).

template<typename T>
bool __autoRegisteringFunction()  {
    std::cout << "Registering: " << typeid(T).name() << std::endl;
    return true;
}

All identifiers with a double underscore are reserved. The rules for underscores at the beginning of identifiers are complex; prefer not to use them at the start (even if you know the rules as everybody else does not and will get it wrong).


Again the double underscore.

template<typename T>
struct __AutoRegisteringClass {
    inline static const bool __registered = __autoRegisteringFunction<T>();
};

I am assuming the advantage here is that this does not take any space in the object while the original method does. Again the double underscore.

#define REGISTER_CLASS() \
    const void* __autoRegisteringMethod() const { \
        return &__AutoRegisteringClass<decltype(*this)>::registered; \
    }
                                                     // ^^^ Forgot double underscore here.

No need for return 0; in main.

int main() {
    return 0;
}
\$\endgroup\$
2
\$\begingroup\$

Could the pattern I'm willing to use be somehow simplified?

If the incantation is simple enough, you don't need to wrap it in a macro to make it usable. E.g.

class Foo
{
    inline static auto registered = register_class<Foo>();
};

To answer the question raised elsewhere, as of C++17 the above definition inside the class (use the inline keyword and provide an initializer there) means you don't need to provide a separate definition for the variable as well.

If you will have multiple things to register that are all provided together, you could use a single variadic template to handle all of them in one go:

    inline static auto registered = register_classes<Foo,Bar,Baz,C,D,Etc>();
    // defined in any convenient location after all the nominated classes have been defined.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.