首页 > 解决方案 > C++17 中的通用工厂机制

问题描述

我想为一组派生类实现一个通用工厂机制,它不仅可以通用地实现工厂函数来创建该类的对象,还可以实现其他模板类的创建者,这些模板类将其中一个派生类作为模板参数.

理想情况下,解决方案将仅使用 C++17 功能(无依赖关系)。

考虑这个例子

#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();
}

运行

我正在寻找一种机制,该机制允许我在不列出所有类的情况下实现两者foo_factory,这样一旦我添加例如作为附加派生类bar_factory,它们就不需要更新。FooD理想情况下,不同的 Foo 衍生品会以某种方式“自我注册”,但将它们全部放在一个中心位置也是可以接受的。

编辑:

基于评论/答案的一些澄清:

编辑2:

在讨论期间证明相关的其他约束:

对于更多内容context,您可以将不同的 Foo 和它的模板参数视为类似于的类,Eigen::Matrix<Scalar>而 Bar 是与 Ceres 一起使用的成本函子。bar 工厂像指针一样返回ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>对象ceres::CostFunction*

编辑3:

根据@Julius 的回答,我创建了一个适用于作为模板和模板模板的 Bars 的解决方案。我怀疑可以使用可变参数模板模板将其统一bar_tmpl_factory并集成到一个函数中(这是一回事吗?)。bar_ttmpl_factory

运行

去做:

我认为问题已得到回答,如果有的话,以上几点应该是单独的问题。

标签: c++templatespolymorphismc++17factory

解决方案


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

这让我们可以在没有元组开销的情况下处理类型包。

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{};

这让我们可以将类型作为值来处理。

现在类型标签映射是一个接受类型标签并返回另一个类型标签的函数。

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 > >;
  }
};

这需要一个模板类型映射并将其转换为标签映射。

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)... );
}

这让我们可以在一堆类型上测试一个条件,并在“成功”的那个上运行一个操作。

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>...
    );
  }
};

现在我们要创建一些模板类的共享指针。

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)...); };
  }
};

这样就可以给共享指针一个类型。

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

现在我们可以在编译时组合函数对象。

接下来接线。

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;

完成

最初的设计实际上是带有 lambda 的,而不是那些structs forshared_ptr_maker等。

两者都make_foos具有make_bars零运行时状态。


推荐阅读