Templated Automatic Factory Registration

Viewed 236

Imagine I had some objects that were all related to some interface class base. However, all of these objects are templated by some integer DIM (dimension). I have assumed all derivatives of base have a static member int number() and a static string InputName. The idea is to register this static method number(). (Really each class will have whole static interface.)

Registration.hpp

template <int DIM>
class objectRegistry
{
  public:
    template<typename T>
    Register() 
   {
      //something like interface_map[T::InputName] = T::number;
   }
  private:
    static inline std::map<std::string, std::function<int ()>> interface_map;
}

Header1.hpp

template <int DIM>
class base
{
  public:
    static inline const std::string InputName = "base";
    static int number() { return 1; };
    base(){};
};
//todo: Some Registration Here

Header2.hpp

template <int DIM>
class derived : public base<DIM>, public AutomaticRegister<DIM, derived<DIM>>
{
  public:
    static inline std::string InputName = "derived";
    static int number() { return 4; };
    derived(){};
};

//todo: Some Registration Here

hearder3.hpp

template <int DIM, typename extra_type>
class derived2 : public base<DIM>, public AutomaticRegister<DIM, derived2<DIM,extra_type>>
{
  public:
    static inline std::string InputName  = "derived2 " + std::string(typeid(extra_type).name());
    static int number() { return 5; };

    extra_type member;
};

//todo: Some Registration Here

Can I create an automatic registration system for derived objects (all derived from base); ideally, one that exists in the same header file as the object definition. I would like, for example, objectRegistry<3> to know that derived<3>, derived2<3,int>, and derived<3,double> exist. I have tried these methods:

Best way to for C++ types to self register in a list? https://www.bfilipek.com/2018/02/factory-selfregister.html

However, because everything is buried in the template< int DIM>, it is never instantiated.

Is there a way I can force derived to instantiated when objectRegistry is instantiated with a particular template value?

1 Answers

Either of the two approaches you linked to will work.

Your problem is that you are using class templates, not classes.

If you did this

class Something : public AutomaticRegister<Something>
{
    // ...
};

then you would get automatic registry, because Something is a class.

You have class templates, which are not anything like a type at all. The registration happens by instantiating the registration class, which is a base class of your class template.

So, in order to instantiate the registration class, you need the stuff you want registered to be treated as a type. Thus, you need to instantiate some part of the class, either by creating an instance of one of the templates...

derived2<1, double> d2_1_double;

or by explicitly instantiating the entire class template...

template class derived2<1, double>;

or by explicitly instantiating some member of the class template, like the number function...

template int derived2<1, double>::number();

or by creating an actual derived class...

struct d2_1_double : derived2<1, double> { };

or some other way to stamp out a class from a class template.

However, a very minor change to the registration class template (adding a type member alias) gives us a mechanism to register them explicitly in bulk, and does not require inheriting from the registration mechanism.

To demonstrate, I added a bit of extremely simple non-production-quality code. To do so, I added a non-standard function to get a unique name for a type that will work for gcc and clang, but no idea about other compilers. It is not necessary, just makes it easier for me.

#include <functional>
#include <iostream>
#include <string_view>
#include <unordered_map>

template <typename ... Ts> struct TypeList { };

template <typename T>
constexpr auto
fname()
{
    return __PRETTY_FUNCTION__;
}

class Registry
{
    std::unordered_map<std::string_view, std::function<int()>> map;

public:
    void insert(std::string_view key, std::function<int()> value) {
        assert(map.find(key) == map.end());
        std::cout << "Register \"" << key << "\", " << value() << '\n';
        map[key] = std::move(value);
    }
    int operator()(std::string_view key) const {
        return map.at(key)();
    }
};

template <int DIM>
Registry & registry()
{
    static Registry result;
    return result;
}

And here is the stuff to do the auto-registration, a modified version of the answer from one of your links.

template <typename T>
class AutoRegister
{
    struct helper {
        helper() { registry<T::dim>().insert(fname<T>(), T::number); }
    };
    /* inline */ static helper h;
    template<helper&> struct ref { using type = AutoRegister; };
public:
    using type = typename ref<h>::type;
};
// NOTE: A bug in gcc forces this usage rather than just using inline.
template <typename T>
typename AutoRegister<T>::helper AutoRegister<T>::h;

Then, with some class templates similar to yours...

template <int DIM>
struct Bar
{
    static constexpr int dim = DIM;
    static int number() { return dim*100 + 99; }
};
template <int DIM, typename T>
struct Baz
{
    static constexpr int dim = DIM;
    static int number() { return dim*100 + 86; }
};
template <int DIM, typename ... Ts>
struct Foo
{
    static constexpr int dim = DIM;
    static int number() { return dim*100 + 42; }
};

and a helper alias template...

template <typename ... Ts>
using RegisterTypes = TypeList<typename AutoRegister<Ts>::type...>;

We can register the stuff we want. The second one has some duplicates just to show that stuff gets registered once.

using Registered = RegisterTypes<Bar<0>, Bar<1>, Baz<1>, Foo<1>>;
using Registered2 = RegisterTypes<Bar<2>, Bar<1>, Baz<1>, Foo<1>>;

int main()
{
}

Running the program results in the following output...

Register "auto fname() [T = Bar<0>]", 99
Register "auto fname() [T = Bar<1>]", 199
Register "auto fname() [T = Baz<1, int>]", 186
Register "auto fname() [T = Foo<1, int>]", 142
Register "auto fname() [T = Bar<2>]", 299
Register "auto fname() [T = Foo<1, int, double>]", 142```

Related