C++17 中的通用工厂机制
Generic factory mechanism in C++17
我想为一组派生的 classes 实现一个通用的工厂机制,这样我不仅可以通用地实现一个工厂函数来创建 class 的对象,还可以实现创建者其他模板 classes 将派生的 classes.
之一作为模板参数
理想情况下,解决方案将仅使用 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();
}
我正在寻找一种机制,允许我在不列出所有 class 的情况下实现 foo_factory
和 bar_factory
,这样它们就不需要在我更新后更新添加例如 FooD
作为额外派生的 class。理想情况下,不同的 Foo 衍生物会以某种方式 "self-register",但将它们全部列在一个中心位置也是可以接受的。
编辑:
根据评论/答案的一些澄清:
- 在我的例子中,有必要使用(类似)字符串调用工厂,因为工厂的调用者使用
Foo
/ BarInterface
的多态性,即他们不知道关于具体派生的classes。另一方面,在 Bar 中,我们希望使用派生的 Foo classes 的模板方法并促进内联,这就是为什么我们真正需要模板派生的 Bar
classes(而不是访问 Foo通过一些基础-class 接口的对象)。
- 我们可以假设所有派生的 Foo classes 都在一个地方定义(因此,如果需要,我们可以在同一个地方将它们全部列出一次的手动注册是可以接受的)。然而,他们并不知道 Bar 的存在,事实上我们有多个不同的 classes,如
BarInterface
和 Bar
。因此,我们不能像 foo_factory
那样创建 Bar 的 "constructor objects" 并将它们保存在地图中。我认为需要的是所有派生 Foo 类型的某种 "compile-time map"(或列表),这样在定义 bar_factory 时,编译器可以迭代它们,但我不知道如何做到这一点...
编辑2:
证明相关的其他限制条件 :
- 模板和模板模板: Foo 实际上是模板(带有一个 class 参数),而 Bar 是模板模板,将具体的 Foo 作为模板参数。 Foo 模板没有特化并且都具有相同的 "name",因此查询任何具体类型都可以。特别是
SpecificFoo<double>::name
始终有效。 @Julius 的回答已经得到扩展以促进这一点。对于@Yakk,可能也可以这样做(但我需要一些时间来详细弄清楚)。
- 灵活的 bar 工厂代码: Bar 的工厂不仅仅是调用构造函数。它还传递一些参数并进行一些类型转换(特别是,它可能具有 Foo 引用,应该
dynamic_cast
到相应的具体派生 Foo)。因此,允许在 bar_factory 定义期间内联编写此代码的解决方案对我来说似乎最易读。 @Julius 的回答在这里很有效,即使带有元组的循环代码有点冗长。
- 使 "single place" 列出 Foos 更加简单: 从目前的答案来看,我相信对我来说要走的路是有一个 foo 的编译时列表类型和迭代它们的方法。有两个答案在一个中心位置定义了 Foo 类型(或模板)的列表(使用
types
模板或元组),这已经很棒了。但是,由于其他原因,我已经在同一个中心位置有一个宏调用列表,每个 foo 一个,例如 DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...
。 FooTypes
的声明能否以某种方式利用它,所以我不必再次列出它们?我想这样的类型列表不能迭代声明(附加到已经存在的列表),或者可以吗?在没有它的情况下,可能有一些宏观魔法是可能的。也许总是重新定义并因此附加到 DECLARE_FOO
调用中的预处理器列表,然后最后一些 "iterate over loop" 来定义 FooTypes
类型列表。 IIRC boost 预处理器具有循环列表的功能(尽管我不想要 boost 依赖)。
对于更多 context
,您可以将不同的 Foo 和它的模板参数视为 class 类似于 Eigen::Matrix<Scalar>
,而 Bar 是与 Ceres 一起使用的成本函子.酒吧工厂 returns 对象 ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>
作为 ceres::CostFunction*
指针。
编辑3:
基于@Julius 的回答,我创建了一个解决方案,该解决方案适用于既是模板又是模板模板的栏。我怀疑可以使用可变变量模板模板将 bar_tmpl_factory
和 bar_ttmpl_factory
统一到一个函数中(是这样吗?)。
待办事项:
合并bar_tmpl_factory
和bar_ttmpl_factory
上面的点Making the "single place" listing the Foos even simpler
可能会用@Yakk 的types
模板替换元组的使用(但在某种程度上,创建者函数可以在所有 foo 类型的循环调用点内联定义).
我认为问题已经回答,如果有的话,以上几点应该是单独的问题。
像下面这样编写一个允许在 class 站点注册的通用工厂:
template <typename Base>
class Factory {
public:
template <typename T>
static bool Register(const char * name) {
get_mapping()[name] = [] { return std::make_unique<T>(); };
return true;
}
static std::unique_ptr<Base> factory(const std::string & name) {
auto it = get_mapping().find(name);
if (it == get_mapping().end())
return {};
else
return it->second();
}
private:
static std::map<std::string, std::function<std::unique_ptr<Base>()>> & get_mapping() {
static std::map<std::string, std::function<std::unique_ptr<Base>()>> mapping;
return mapping;
}
};
然后像这样使用它:
struct FooA: Foo {
static constexpr char const* name = "A";
inline static const bool is_registered = Factory<Foo>::Register<FooA>(name);
inline static const bool is_registered_bar = Factory<BarInterface>::Register<Bar<FooA>>(name);
void hello() override { std::cout << "Hello " << name << std::endl; }
};
和
std::unique_ptr<Foo> foo_factory(const std::string& name) {
return Factory<Foo>::factory(name);
}
注意: 无法保证 class 会被注册。如果没有其他依赖项,编译器可能会决定不包含翻译单元。将所有 classes 简单地注册在一个中心位置可能会更好。另请注意,自注册实现取决于内联变量 (C++17)。不是强依赖,可以通过在header中声明布尔值并在CPP中定义来摆脱它(这使得自注册更加丑陋,更容易注册失败)。
编辑
- 与其他答案相比,这个答案的缺点是它在启动期间而不是在编译期间执行注册。另一方面,这使代码更简单。
上面的例子假定 Bar<T>
的定义移到了 Foo
上面。如果那是不可能的,那么注册可以在初始化函数中完成,在 cpp:
// If possible, put at the header file and uncomment:
// inline
const bool barInterfaceInitialized = [] {
Factory<Foo>::Register<FooA>(FooA::name);
Factory<Foo>::Register<FooB>(FooB::name);
Factory<Foo>::Register<FooC>(FooC::name);
Factory<BarInterface>::Register<Bar<FooA>>(FooA::name);
Factory<BarInterface>::Register<Bar<FooB>>(FooB::name);
Factory<BarInterface>::Register<Bar<FooC>>(FooC::name);
return true;
}();
在C++17中,我们可以应用fold表达式来简化生成函数std::make_unique<FooA>()
、std::make_unique<FooB>()
等到工厂class中的存储过程案例.
首先,为方便起见,让我们定义以下类型别名Generator
,它描述了每个生成函数的类型[](){ return std::make_unique<T>(); }
:
template<typename T>
using Generator = std::function<std::unique_ptr<T>(void)>;
接下来,我们定义以下相当通用的仿函数 createFactory
,其中 returns 每个工厂作为哈希映射 std::unordered_map
。
在这里,我使用逗号运算符应用折叠表达式。
比如,createFactory<BarInterface, Bar, std::tuple<FooA, FooB, FooC>>()()
returns 你的函数对应的hash map bar_factory
:
template<typename BaseI, template<typename> typename I, typename T>
void inserter(std::unordered_map<std::string_view, Generator<BaseI>>& map)
{
map.emplace(T::name, [](){ return std::make_unique<I<T>>(); });
}
template<typename BaseI, template<typename> class I, typename T>
struct createFactory {};
template<typename BaseI, template<typename> class I, typename... Ts>
struct createFactory<BaseI, I, std::tuple<Ts...>>
{
auto operator()()
{
std::unordered_map<std::string_view, Generator<BaseI>> map;
(inserter<BaseI, I, Ts>(map), ...);
return map;
}
};
这个仿函数使我们能够将 FooA, FooB, FooC, ...
全部列在一个中心位置,如下所示:
DEMO(我还在基础classes中添加了虚拟析构函数)
template<typename T>
using NonInterface = T;
// This can be written in one central place.
using FooTypes = std::tuple<FooA, FooB, FooC>;
int main()
{
const auto foo_factory = createFactory<Foo, NonInterface, FooTypes>()();
const auto foo = foo_factory.find("A");
if(foo != foo_factory.cend()){
foo->second()->hello();
}
const auto bar_factory = createFactory<BarInterface, Bar, FooTypes>()();
const auto bar = bar_factory.find("C");
if(bar != bar_factory.cend()){
bar->second()->world();
}
return 0;
}
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...
这是一个基本选项:
#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;
}
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{};
这让我们可以使用类型作为值。
现在类型标签映射是一个接受类型标签和 returns 另一个类型标签的函数。
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)... );
}
这让我们可以测试一堆类型的条件,运行 一个操作 "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>...
);
}
};
现在我们要创建一些模板的共享指针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)...); };
}
};
这样就可以为共享指针指定类型。
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;
和Done.
最初的设计实际上是 c++20 使用 lambda 而不是那些 shared_ptr_maker
等的 struct
。
make_foos
和 make_bars
都具有零 运行 时间状态。
我想为一组派生的 classes 实现一个通用的工厂机制,这样我不仅可以通用地实现一个工厂函数来创建 class 的对象,还可以实现创建者其他模板 classes 将派生的 classes.
之一作为模板参数理想情况下,解决方案将仅使用 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();
}
我正在寻找一种机制,允许我在不列出所有 class 的情况下实现 foo_factory
和 bar_factory
,这样它们就不需要在我更新后更新添加例如 FooD
作为额外派生的 class。理想情况下,不同的 Foo 衍生物会以某种方式 "self-register",但将它们全部列在一个中心位置也是可以接受的。
编辑:
根据评论/答案的一些澄清:
- 在我的例子中,有必要使用(类似)字符串调用工厂,因为工厂的调用者使用
Foo
/BarInterface
的多态性,即他们不知道关于具体派生的classes。另一方面,在 Bar 中,我们希望使用派生的 Foo classes 的模板方法并促进内联,这就是为什么我们真正需要模板派生的Bar
classes(而不是访问 Foo通过一些基础-class 接口的对象)。 - 我们可以假设所有派生的 Foo classes 都在一个地方定义(因此,如果需要,我们可以在同一个地方将它们全部列出一次的手动注册是可以接受的)。然而,他们并不知道 Bar 的存在,事实上我们有多个不同的 classes,如
BarInterface
和Bar
。因此,我们不能像foo_factory
那样创建 Bar 的 "constructor objects" 并将它们保存在地图中。我认为需要的是所有派生 Foo 类型的某种 "compile-time map"(或列表),这样在定义 bar_factory 时,编译器可以迭代它们,但我不知道如何做到这一点...
编辑2:
证明相关的其他限制条件
- 模板和模板模板: Foo 实际上是模板(带有一个 class 参数),而 Bar 是模板模板,将具体的 Foo 作为模板参数。 Foo 模板没有特化并且都具有相同的 "name",因此查询任何具体类型都可以。特别是
SpecificFoo<double>::name
始终有效。 @Julius 的回答已经得到扩展以促进这一点。对于@Yakk,可能也可以这样做(但我需要一些时间来详细弄清楚)。 - 灵活的 bar 工厂代码: Bar 的工厂不仅仅是调用构造函数。它还传递一些参数并进行一些类型转换(特别是,它可能具有 Foo 引用,应该
dynamic_cast
到相应的具体派生 Foo)。因此,允许在 bar_factory 定义期间内联编写此代码的解决方案对我来说似乎最易读。 @Julius 的回答在这里很有效,即使带有元组的循环代码有点冗长。 - 使 "single place" 列出 Foos 更加简单: 从目前的答案来看,我相信对我来说要走的路是有一个 foo 的编译时列表类型和迭代它们的方法。有两个答案在一个中心位置定义了 Foo 类型(或模板)的列表(使用
types
模板或元组),这已经很棒了。但是,由于其他原因,我已经在同一个中心位置有一个宏调用列表,每个 foo 一个,例如DECLARE_FOO(FooA, "A") DECLARE_FOO(FooB, "B") ...
。FooTypes
的声明能否以某种方式利用它,所以我不必再次列出它们?我想这样的类型列表不能迭代声明(附加到已经存在的列表),或者可以吗?在没有它的情况下,可能有一些宏观魔法是可能的。也许总是重新定义并因此附加到DECLARE_FOO
调用中的预处理器列表,然后最后一些 "iterate over loop" 来定义FooTypes
类型列表。 IIRC boost 预处理器具有循环列表的功能(尽管我不想要 boost 依赖)。
对于更多 context
,您可以将不同的 Foo 和它的模板参数视为 class 类似于 Eigen::Matrix<Scalar>
,而 Bar 是与 Ceres 一起使用的成本函子.酒吧工厂 returns 对象 ceres::AutoDiffCostFunction<CostFunctor<SpecificFoo>, ...>
作为 ceres::CostFunction*
指针。
编辑3:
基于@Julius 的回答,我创建了一个解决方案,该解决方案适用于既是模板又是模板模板的栏。我怀疑可以使用可变变量模板模板将 bar_tmpl_factory
和 bar_ttmpl_factory
统一到一个函数中(是这样吗?)。
待办事项:
合并bar_tmpl_factory
和bar_ttmpl_factory
上面的点Making the "single place" listing the Foos even simpler
可能会用@Yakk 的types
模板替换元组的使用(但在某种程度上,创建者函数可以在所有 foo 类型的循环调用点内联定义).
我认为问题已经回答,如果有的话,以上几点应该是单独的问题。
像下面这样编写一个允许在 class 站点注册的通用工厂:
template <typename Base>
class Factory {
public:
template <typename T>
static bool Register(const char * name) {
get_mapping()[name] = [] { return std::make_unique<T>(); };
return true;
}
static std::unique_ptr<Base> factory(const std::string & name) {
auto it = get_mapping().find(name);
if (it == get_mapping().end())
return {};
else
return it->second();
}
private:
static std::map<std::string, std::function<std::unique_ptr<Base>()>> & get_mapping() {
static std::map<std::string, std::function<std::unique_ptr<Base>()>> mapping;
return mapping;
}
};
然后像这样使用它:
struct FooA: Foo {
static constexpr char const* name = "A";
inline static const bool is_registered = Factory<Foo>::Register<FooA>(name);
inline static const bool is_registered_bar = Factory<BarInterface>::Register<Bar<FooA>>(name);
void hello() override { std::cout << "Hello " << name << std::endl; }
};
和
std::unique_ptr<Foo> foo_factory(const std::string& name) {
return Factory<Foo>::factory(name);
}
注意: 无法保证 class 会被注册。如果没有其他依赖项,编译器可能会决定不包含翻译单元。将所有 classes 简单地注册在一个中心位置可能会更好。另请注意,自注册实现取决于内联变量 (C++17)。不是强依赖,可以通过在header中声明布尔值并在CPP中定义来摆脱它(这使得自注册更加丑陋,更容易注册失败)。
编辑
- 与其他答案相比,这个答案的缺点是它在启动期间而不是在编译期间执行注册。另一方面,这使代码更简单。
上面的例子假定
Bar<T>
的定义移到了Foo
上面。如果那是不可能的,那么注册可以在初始化函数中完成,在 cpp:// If possible, put at the header file and uncomment: // inline const bool barInterfaceInitialized = [] { Factory<Foo>::Register<FooA>(FooA::name); Factory<Foo>::Register<FooB>(FooB::name); Factory<Foo>::Register<FooC>(FooC::name); Factory<BarInterface>::Register<Bar<FooA>>(FooA::name); Factory<BarInterface>::Register<Bar<FooB>>(FooB::name); Factory<BarInterface>::Register<Bar<FooC>>(FooC::name); return true; }();
在C++17中,我们可以应用fold表达式来简化生成函数std::make_unique<FooA>()
、std::make_unique<FooB>()
等到工厂class中的存储过程案例.
首先,为方便起见,让我们定义以下类型别名Generator
,它描述了每个生成函数的类型[](){ return std::make_unique<T>(); }
:
template<typename T>
using Generator = std::function<std::unique_ptr<T>(void)>;
接下来,我们定义以下相当通用的仿函数 createFactory
,其中 returns 每个工厂作为哈希映射 std::unordered_map
。
在这里,我使用逗号运算符应用折叠表达式。
比如,createFactory<BarInterface, Bar, std::tuple<FooA, FooB, FooC>>()()
returns 你的函数对应的hash map bar_factory
:
template<typename BaseI, template<typename> typename I, typename T>
void inserter(std::unordered_map<std::string_view, Generator<BaseI>>& map)
{
map.emplace(T::name, [](){ return std::make_unique<I<T>>(); });
}
template<typename BaseI, template<typename> class I, typename T>
struct createFactory {};
template<typename BaseI, template<typename> class I, typename... Ts>
struct createFactory<BaseI, I, std::tuple<Ts...>>
{
auto operator()()
{
std::unordered_map<std::string_view, Generator<BaseI>> map;
(inserter<BaseI, I, Ts>(map), ...);
return map;
}
};
这个仿函数使我们能够将 FooA, FooB, FooC, ...
全部列在一个中心位置,如下所示:
DEMO(我还在基础classes中添加了虚拟析构函数)
template<typename T>
using NonInterface = T;
// This can be written in one central place.
using FooTypes = std::tuple<FooA, FooB, FooC>;
int main()
{
const auto foo_factory = createFactory<Foo, NonInterface, FooTypes>()();
const auto foo = foo_factory.find("A");
if(foo != foo_factory.cend()){
foo->second()->hello();
}
const auto bar_factory = createFactory<BarInterface, Bar, FooTypes>()();
const auto bar = bar_factory.find("C");
if(bar != bar_factory.cend()){
bar->second()->world();
}
return 0;
}
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...
这是一个基本选项:
#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;
}
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{};
这让我们可以使用类型作为值。
现在类型标签映射是一个接受类型标签和 returns 另一个类型标签的函数。
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)... );
}
这让我们可以测试一堆类型的条件,运行 一个操作 "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>...
);
}
};
现在我们要创建一些模板的共享指针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)...); };
}
};
这样就可以为共享指针指定类型。
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;
和Done.
最初的设计实际上是 c++20 使用 lambda 而不是那些 shared_ptr_maker
等的 struct
。
make_foos
和 make_bars
都具有零 运行 时间状态。