Search code examples
c++templatespolymorphismc++17factory

Generic factory mechanism in C++17


I would like to implement a generic factory mechanism for a set of derived classes that allows me to generically implement not only a factory function to create objects of that class, but also creators of other template classes which take as template arguments one of the derived classes.

Ideally a solution would only use C++17 features (no dependencies).

Consider this example

#include <iostream>
#include <string>
#include <memory>

struct Foo {
    virtual ~Foo() = default;
    virtual void hello() = 0;
};

struct FooA: Foo { 
    static constexpr char const* name = "A";
    void hello() override { std::cout << "Hello " << name << std::endl; }
};

struct FooB: Foo { 
    static constexpr char const* name = "B";
    void hello() override { std::cout << "Hello " << name << std::endl; }
};

struct FooC: Foo { 
    static constexpr char const* name = "C";
    void hello() override { std::cout << "Hello " << name << std::endl; }
};

struct BarInterface {
    virtual ~BarInterface() = default;
    virtual void world() = 0;
};

template <class T>
struct Bar: BarInterface {
    void world() { std::cout << "World " << T::name << std::endl; }
};

std::unique_ptr<Foo> foo_factory(const std::string& name) {
    if (name == FooA::name) {
        return std::make_unique<FooA>();
    } else if (name == FooB::name) {
        return std::make_unique<FooB>();
    } else if (name == FooC::name) {
        return std::make_unique<FooC>();
    } else {
        return {};
    }
}

std::unique_ptr<BarInterface> bar_factory(const std::string& foo_name) {
    if (foo_name == FooA::name) {
        return std::make_unique<Bar<FooA>>();
    } else if (foo_name == FooB::name) {
        return std::make_unique<Bar<FooB>>();
    } else if (foo_name == FooC::name) {
        return std::make_unique<Bar<FooC>>();
    } else {
        return {};
    }
}

int main()
{
    auto foo = foo_factory("A");
    foo->hello();
    auto bar = bar_factory("C");
    bar->world();
}

run it

I am looking for a mechanism that would allow me to implement both foo_factory and bar_factory without listing all classes, such that they do not need to be updated once I add for example FooD as an additional derived class. Ideally, the different Foo derivatives would somehow "self-register", but listing them all in one central place is also acceptable.

Edit:

Some clarifications based on comments / answers:

  • It is necessary in my case to invoke the factories with (something like) a string, since the callers of the factories use polymorphism with Foo / BarInterface, i.e. they don't know about the concrete derived classes. On the other hand in Bar we want to use template methods of the derived Foo classes and facilitate inlining, that's why we really need the templated derived Bar classes (rather than accessing Foo objects through some base-class interface).
  • We can assume that all derived Foo classes are defined in one place (and a manual registration where we list them all once in the same place is therefore acceptable, if necessary). However, they do not know about the existence of Bar, and in fact we have multiple different classes like BarInterface and Bar. So we cannot create "constructor objects" of Bar and save them in a map the same way we can do it for a foo_factory. What I think is needed is some kind of "compile-time map" (or list) of all the derived Foo types, such that when defining the bar_factory, the compiler can iterate over them, but I don't know how to do that...

Edit2:

Additional constraints that proofed to be relevant during discussion:

  • Templates and template templates: The Foo are actually templates (with a single class argument) and the Bar are template templates taking a concrete Foo as template argument. The Foo templates have no specializations and all have the same "name", so querying any concrete type is fine. In particular SpecificFoo<double>::name is always valid. @Julius' answer has been extended to facilitate this already. For @Yakk's the same can probably be done (but it will take me some time for figure it out in detail).
  • Flexible bar factory code: The factory for Bar does a little more than just call the constructor. It also passes some arguments and does some type casting (in particular, it may have Foo references that should be dynamic_cast to the corresponding concrete derived Foo). Therefore a solution that allows to write this code inline during definition of the bar_factory seems most readable to me. @Julius' answer works great here, even if the loop code with tuples is a little verbose.
  • Making the "single place" listing the Foos even simpler: From the answers so far I believe the way to go for me is having a compile-time list of foo types and a way to iterate over them. There are two answers that define a list of Foo types (or templates) in one central place (either with a types template or with tuples), which is already great. However, for other reasons I already have in the same central place a list of macro calls, one for each foo, like DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") .... Can the declaration of FooTypes be somehow take advantage of that, so I don't have to list them again? I guess such type lists cannot be declared iteratively (appending to an already existing list), or can it? In the absence of that, probably with some macro magic it would be possible. Maybe always redefining and thus appending to a preprocessor list in the DECLARE_FOO calls, and then finally some "iterate over loop" to define the FooTypes type list. IIRC boost preprocessor has facilities to loop over lists (although I don't want a boost dependency).

For some more context, you can think of the different Foo and it's template argument as classes similar to Eigen::Matrix<Scalar> and the Bar are cost functors to be used with Ceres. The bar factory returns objects like ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...> as ceres::CostFunction* pointers.

Edit3:

Based on @Julius' answer I created a solution that works with Bars that are templates as well as template templates. I suspect one could unify bar_tmpl_factory and bar_ttmpl_factory into one function using variadic variadic template templates (is that a thing?).

run it

TODO:

  • combine bar_tmpl_factory and bar_ttmpl_factory
  • the point Making the "single place" listing the Foos even simpler from above
  • maybe replacing the use of tuples with @Yakk's types template (but in a way such that the creator function can be defined inline at the call site of the loop over all foo types).

I consider the question answered and if anything the above points should be separate questions.


Solution

  • What I think is needed is some kind of "compile-time map" (or list) of all the derived Foo types, such that when defining the bar_factory, the compiler can iterate over them, but I don't know how to do that...

    Here is one basic option:

    #include <cassert>
    
    #include <tuple>
    #include <utility>
    
    #include "foo_and_bar_without_factories.hpp"
    
    ////////////////////////////////////////////////////////////////////////////////
    
    template<std::size_t... indices, class LoopBody>
    void loop_impl(std::index_sequence<indices...>, LoopBody&& loop_body) {
      (loop_body(std::integral_constant<std::size_t, indices>{}), ...);
    }
    
    template<std::size_t N, class LoopBody>
    void loop(LoopBody&& loop_body) {
      loop_impl(std::make_index_sequence<N>{}, std::forward<LoopBody>(loop_body));
    }
    
    ////////////////////////////////////////////////////////////////////////////////
    
    using FooTypes = std::tuple<FooA, FooB, FooC>;// single registration
    
    std::unique_ptr<Foo> foo_factory(const std::string& name) {
      std::unique_ptr<Foo> ret{};
    
      constexpr std::size_t foo_count = std::tuple_size<FooTypes>{};
    
      loop<foo_count>([&] (auto i) {// `i` is an std::integral_constant
        using SpecificFoo = std::tuple_element_t<i, FooTypes>;
        if(name == SpecificFoo::name) {
          assert(!ret && "TODO: check for unique names at compile time?");
          ret = std::make_unique<SpecificFoo>();
        }
      });
    
      return ret;
    }
    
    std::unique_ptr<BarInterface> bar_factory(const std::string& name) {
      std::unique_ptr<BarInterface> ret{};
    
      constexpr std::size_t foo_count = std::tuple_size<FooTypes>{};
    
      loop<foo_count>([&] (auto i) {// `i` is an std::integral_constant
        using SpecificFoo = std::tuple_element_t<i, FooTypes>;
        if(name == SpecificFoo::name) {
          assert(!ret && "TODO: check for unique names at compile time?");
          ret = std::make_unique< Bar<SpecificFoo> >();
        }
      });
    
      return ret;
    }