0

I've been trying to implement the Self Registering Factory pattern in my project and, after trying a bunch of ways to do it, I've settled for this solution.

Unfortunately, I've stumbled upon a problem where my code isn't compiling because my base class doesn't have any arguments to pass. There is a comment in the provided link detailing this exact issue but I must say that I don't understand why it doesn't work and how to make it work if possible.

Here is the error I get when compiling:

could not convert ‘std::make_unique(_Args&& ...) [with _Tp = ObjectA; _Args = {}; typename std::_MakeUniq<_Tp>::__single_object = std::unique_ptr<ObjectA, std::default_delete<ObjectA> >]()’ from ‘unique_ptr<ObjectA,default_delete<ObjectA>>’ to ‘unique_ptr<BaseClass,default_delete<BaseClass>>’
   40 |                 return std::make_unique<T>(std::forward<Args>(args)...);
      |                                                                       ^
      |                                                                       |
      |                                                                       unique_ptr<ObjectA,default_delete<ObjectA>>

For the sake of clarity, I'll post the code and example of classes I'm trying to implement with it.

selfregisteringfactory.h

#include <memory>
#include <unordered_map>
#include <string>

#include <cstdlib>
#include <cxxabi.h>


std::string demangle(const char *name) {
    int status = -4;

    std::unique_ptr<char, void (*)(void*)> res{
        abi::__cxa_demangle(name, NULL, NULL, &status), free};

    return (status == 0) ? res.get() : name;
}

template<class Base, class... Args>
class SelfRegisteringFactory {
public:
    template<class ... T>
    static std::unique_ptr<Base> make(const std::string &name, T&&... args) {
        return data().at(name)(std::forward<T>(args)...);
    }

    friend Base;

    template <class T>
    class Registrar : Base {
        friend T;

        static bool registerT() {
              const auto name = demangle(typeid(T).name());
              SelfRegisteringFactory::data()[name] = [](Args... args) -> std::unique_ptr<Base> {
                return std::make_unique<T>(std::forward<Args>(args)...);
              };
              return true;
        }
        static bool registered;

    private:
        Registrar() : Base(Key{}) { (void)registered;};
    };

private:
    class Key {
        Key(){};
        template <class T> friend class Registrar;
    };

    using FuncType = std::unique_ptr<Base> (*)(Args...);
    SelfRegisteringFactory() = default;
    static std::unordered_map<std::string, FuncType> &data(){
        static std::unordered_map<std::string, FuncType> s;
        return s;
    }
};

template <class Base, class... Args>
template <class T>
bool SelfRegisteringFactory<Base, Args...>::Registrar<T>::registered =
        SelfRegisteringFactory<Base, Args...>::Registrar<T>::registerT();

baseclass.h

#include "selfregisteringfactory.h"
#include <string>

class BaseClass : public SelfRegisteringFactory<BaseClass>{
public:
    BaseClass(Key){};
    virtual ~BaseClass() = default;
    virtual void process() = 0;
    virtual std::string getType() = 0;
};

objecta.h

#include "baseclass.h"

class ObjectA: public BaseClass::Registrar<ObjectA>{
public:
    ObjectA();
    virtual ~ObjectA() = default;
    virtual void process();
    virtual std::string getType();
};

objecta.cpp

#include "objecta.h"
#include <iostream>

ObjectA::ObjectA(){

}

void ObjectA::process(){
    std::cout << "This is a process." << std::endl;
}

std::string ObjectA::getType(){
    return "ObjectA";
}

Update As @AlanBirtles pointed out, I've missed the public when writting the Registrar class. The code compiles but when I test it with my unit tests it doesn't seems to register ObjectA. I'm getting the out-of-range exception from the .at().

Here's what my test file looks like:

selfregisteringfactory.test.cpp

#include "catch2/catch.hpp"
#include "catch/fakeit.hpp"

#include "baseclass.h"

using namespace fakeit;

TEST_CASE( "TEST SelfRegisteringFactory class." )
{
    SECTION("Test if adding an OjbjectA to the factory is possible.")
    {   
        auto objA = DeviceCommunication::make("ObjectA");
        REQUIRE(objA ->getType() == "ObjectA");
    }
}
jcracine
  • 1
  • 1
  • Might be related: https://stackoverflow.com/questions/11002641/dynamic-casting-for-unique-ptr – lorro Jul 20 '22 at 17:59
  • @lorro I don't think this is the issue because if you pass arguments like in [this example provided by the original post](http://coliru.stacked-crooked.com/a/11473a649e402831), it compiles just fine. But if you remove them like in [this example](http://coliru.stacked-crooked.com/a/55fe8edc094c88a8) it stops compiling. – jcracine Jul 20 '22 at 18:29
  • typo `class Registrar : Base` -> `class Registrar : public Base`? Otherwise `ObjectA` isn't publically derived from `BaseClass`. https://godbolt.org/z/e15zPYETG – Alan Birtles Jul 20 '22 at 18:35
  • @AlanBirtles That was it! Now I just need to find why ObjectA isn't registering itself when I test it with Catch2. – jcracine Jul 20 '22 at 18:42

0 Answers0