Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 364
Nikolaus Demmel Avatar asked Oct 15 '25 07:10

Nikolaus Demmel


1 Answers

template<class...Ts>struct types_t {};
template<class...Ts>constexpr types_t<Ts...> types{};

that lets us work with bundles of types without the overhead of a tuple.

template<class T>
struct tag_t { using type=T;
  template<class...Ts>
  constexpr decltype(auto) operator()(Ts&&...ts)const {
    return T{}(std::forward<Ts>(ts)...);
  }
};
template<class T>
constexpr tag_t<T> tag{};

this lets us work with types as values.

Now a type tag map is a function that takes a type tag, and returns another type tag.

template<template<class...>class Z>
struct template_tag_map {
  template<class In>
  constexpr decltype(auto) operator()(In in_tag)const{
    return tag< Z< typename decltype(in_tag)::type > >;
  }
};

this takes a template type map and makes it into a tag map.

template<class R=void, class Test, class Op, class T0 >
R type_switch( Test&&, Op&& op, T0&&t0 ) {
  return static_cast<R>(op(std::forward<T0>(t0)));
}

template<class R=void, class Test, class Op, class T0, class...Ts >
auto type_switch( Test&& test, Op&& op, T0&& t0, Ts&&...ts )
{
  if (test(t0)) return static_cast<R>(op(std::forward<T0>(t0)));
  return type_switch<R>( test, op, std::forward<Ts>(ts)... );
}

that lets us test a condition on a bunch of types, and run an operation on the one that "succeeds".

template<class R, class maker_map, class types>
struct named_factory_t;

template<class R, class maker_map, class...Ts>
struct named_factory_t<R, maker_map, types_t<Ts...>>
{
  template<class... Args>
  auto operator()( std::string_view sv, Args&&... args ) const {
    return type_switch<R>(
      [&sv](auto tag) { return decltype(tag)::type::name == sv; },
      [&](auto tag) { return maker_map{}(tag)(std::forward<Args>(args)...); },
      tag<Ts>...
    );
  }
};

now we want to make shared pointers of some template class.

struct shared_ptr_maker {
  template<class Tag>
  constexpr auto operator()(Tag ttag) {
    using T=typename decltype(ttag)::type;
    return [](auto&&...args){ return std::make_shared<T>(decltype(args)(args)...); };
  }
};

so that makes shared pointers given a type.

template<class Second, class First>
struct compose {
  template<class...Args>
  constexpr decltype(auto) operator()(Args&&...args) const {
    return Second{}(First{}( std::forward<Args>(args)... ));
  }
};

now we can compose function objects at compile time.

Next wire it up.

using Foos = types_t<FooA, FooB, FooC>;
constexpr named_factory_t<std::shared_ptr<Foo>, shared_ptr_maker, Foos> make_foos;

constexpr named_factory_t<std::shared_ptr<BarInterface>, compose< shared_ptr_maker, template_tag_map<Bar> >, Foos> make_bars;

and Done.

The original design was actually c++20 with lambdas instead of those structs for shared_ptr_maker and the like.

Both make_foos and make_bars have zero runtime state.

like image 56
Yakk - Adam Nevraumont Avatar answered Oct 18 '25 05:10

Yakk - Adam Nevraumont



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!