有人可以在下面的代码中提出一种避免虚拟模板功能的技术吗?我已经阅读了其他几篇文章,我不知道如何将这些解决方案应用于此案例。
我正在构建一个包含模板化类层次结构的库。我想创建一个“工厂”函数数组,可用于按名称实例化派生类(例如,基于命令行参数)。
如果可能的话,我希望每个派生类能够在它自己的.hpp或.cpp文件中注册自己(而不是必须在某处维护所有可能的派生类的单个列表)。
几乎下面的代码可以正常工作,除了尝试使用虚拟模板功能的致命缺陷。
//
// This code would appear in a library
//
template<class T>
class Base {
public:
Base(const char* /*param*/) {}
};
//
// Description of each derived class.
// We need this non-templated base class so we can store all our
// descriptions in a single vector
//
class DescriptionBase {
private:
const char *description;
const char *name;
public:
DescriptionBase(const char* pDesc, const char* pName) : description(pDesc), name(pName){
// Whenever a Description object is created, it is automatically registered with the
// global descriptionList. This allows us to register derived classes in their own
// .cpp/.hpp file (as opposed to keeping a centralized list of all derived classes).
descriptionList.push_back(this);
}
// FAIL Can't have virtual template functions
virtual template<class T>
Base<T> *make(const char *param) {return new Base<T>(param); }
static vector<DescriptionBase *> descriptionList;
};
//global list of all derived classes
vector<DescriptionBase *> DescriptionBase::descriptionList;
// We use the template to store the type of the derived class
// for use in the make method
template<template<typename> class D>
class Description : public DescriptionBase {
public:
Description(const char* pDesc, const char* pName) : DescriptionBase(pDesc, pName) {}
template<class T>
Base<T> *make(const char *params) {
return new D<T>(params);
}
};
//
// These derived classes may be part of the library, or they may be
// written by users of the library.
//
template<class T>
class DerivedA : public Base<T> {
public:
DerivedA(const char* param) : Base<T>(param) {return;}
};
Description<DerivedA> derivedA("derivedA", "This is the first derived class");
template<class T>
class DerivedB : public Base<T> {
DerivedB(const char* param) : Base<T>(param) {return;}
};
Description<DerivedA> derivedB("derivedA", "This is the second derived class");
//
// Example code written by the user of the library.
//
//
int main(int argc, char *argv[]) {
// Using a descriptionList is just a short-cut here.
// Final code will use a map.
int indexOfDerivedA = 0;
Base<int> *intItem = DescriptionBase::descriptionList[indexOfDerivedA]->make<int>("parameter to derived type's constructor");
Base<char> *charItem = DescriptionBase::descriptionList[indexOfDerivedA]->make<char>("parameter to derived type's constructor");
}
答案 0 :(得分:2)
这是一个编译时标记,并输入:
template<class T>
struct tag_t { constexpr tag_t() {}; };
template<class T> constexpr tag_t tag<T>{};
这是一个类型列表:
template<class...>struct types_t{constexpr types_t(){}; using type=types_t;};
template<class...Ts>constexpr types_t<Ts...> types{};
这会映射类型列表的内容:
template<template<class...>class Z, class types>
struct fmap{};
template<template<class...>class Z, class types>
using fmap_t=typename fmap<Z,types>::type;
template<template<class...>class Z, template<class...>class types, class...Ts>
struct fmap<Z,types<Ts...>> {
using type=types<Z<Ts...>>;
};
现在让我们列出一些工厂:
template<class...Args>
struct build_tagged_sig {
template<class T>
using result = std::unique_ptr<T>(tag_t<T>,Args...);
};
template<template<class...>class Out, class types, class...Args>
using tagged_factories =
fmap_t<
std::function,
fmap_t<
build_tagged_sig<Args...>::template result,
fmap_t< Out, types >
>
>;
这会将types
应用于模板:
template<template<class...>class Z, class types>
struct apply_types {};
template<template<class...>class Z, class types>
using apply_types_t = typename apply_types<Z,types>::type;
template<template<class...>class Z, template<class...>class types, class...Ts>
struct apply_types<Z, types<Ts...>> {
using type=Z<Ts...>;
};
template<template<class...>class Z>
struct applier {
template<class types>
using result=apply_types_t<Z,types>;
};
This SO post shows how to overload multiple lambdas or std::function
s
using my_types = types_t< std::int8_t, std::int16_t, std::int32_t, std::int64_t >;
using magic_factory =
apply_types_t<
overload,
tagged_factories< Base, my_types, const char* > >
>;
是
的重载std::function< std::unique_ptr<Base<std::int8_t>>( tag_t<Base<std::int8_t>>, const char* ) >,
std::function< std::unique_ptr<Base<std::int16_t>>( tag_t<Base<std::int16_t>>, const char* ) >,
std::function< std::unique_ptr<Base<std::int32_t>>( tag_t<Base<std::int32_t>>, const char* ) >,
std::function< std::unique_ptr<Base<std::int64_t>>( tag_t<Base<std::int64_t>>, const char* ) >
我们现在写一个注册工厂:
template<class F, class...Ts>
magic_factory make_poly_factory( types_t<Ts...>, F&& f ) {
return magic_factory(
(void(tag<Ts>), f)...
);
}
template<class F>
magic_factory make_poly_factory( F&& f ) {
return make_poly_factory( my_types{}, f );
}
创建f
的N个副本,并将每个副本存储在一个对象的std::function
中。
获取返回值,您可以通过重载决策调用单个值。
template<class T>
std::unique_ptr<Base<T>> factory_A_impl( tag_t<Base<T>>, const char* param) {
return new DerivedA<T>(param);
}
auto magic = magic_factory( my_types{}, [](auto tag, const char* param){
return factory_A_impl( tag, param );
});
std::unique_ptr<Base<std::int8_t>> bob = magic( tag<std::int8_t>, "hello" );
和bob
是unique_ptr
到Base<std::int8_t>
,在运行时实际上是DerivedA<std::int8_t>
。
这可能有tpyos。
这篇文章的大部分内容都是元编程,用于设置单个对象,使tag_t<T0>
到tag_t<T1>
中的每一个都重载,而不重复自己。您可以手动为4种类型执行此操作。
我使用的重载假设我们没有使用模板参数的单个lambda和类型擦除每个重载,而是一组lambdas。第一种方法可以使触摸更轻松。
最终用户学生只需创建一个功能对象,该对象需要tag_t<X>
和const char*
并返回unique_ptr<X>
并且便宜复制,然后创建一个{{ 1}}来自它(将其类型化 - 将其删除为一组magic_factory
s。)
DescriptionBase变为:
std::function
多态现在在struct Description {
char const* description = 0;
char const* name = 0;
magic_factory factory;
template<class T>
std::unique_ptr<Base<T>>
make(const char *param) {
return factory( tag<T>, param );
}
};
之内。存储magic_factory
的实例,而不是它们的指针,因为它们是值类型多态。
看,轻松。
添加更多模板参数只会增加Description
内容的复杂性,并为fmap
的创建者带来更多责任。
您需要跨产品操作从4个元素的一个列表中生成64种不同类型的类型。它将是magic_factory
的{{1}}。
调用此types_t
。
然后
types_t
并完成了,我们现在有64个重载,签名如下:
my_many_types
现在我们可以手动完成所有这些操作。
建立一个这样的表:
using magic_factory =
apply_types_t<
overload,
tagged_factories< applier<Base>::template result, my_types, const char* > >
>;
...
std::unique_ptr<Base<std::int8_t, std::int16_t, std::int8_t>>(
tag_t<Base<std::int8_t, std::int16_t, std::int8_t>>,
const char*
)
现在是一家神奇的工厂:
template<class T>
using factory_ptr = std::unique_ptr<T>( void*, tag_t<T>, const char* );
using factory_table = std::tuple<
factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int16_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int32_t > >,
factory_ptr< Base< std::int8_t, std::int8_t, std::int64_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int8_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int16_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int32_t > >,
factory_ptr< Base< std::int8_t, std::int16_t, std::int64_t > >,
我们在其中构建一个 factory_ptr< Base< std::int64_t, std::int64_t, std::int64_t > >
>;
类型擦除函数和一个指向其参数的void指针并自行调度。
您也可以使用上述某些机器自动执行此操作。手动表确实提高了效率,因为我们不会像struct magic_factory {
std::unique_ptr<void, void(*)(void*)> state;
factory_table table;
template<class T0, class T1, class T2>
std::unique_ptr<Base<T0, T1, T2>> make( char const* param ) {
auto f = std::get<factory_ptr< Base< T0, T1, T2 > >>( table );
return f( state.get(), param );
}
magic_factory(magic_factory&&)=default;
template<class T,
class=std::enable_if_t<!std::is_same<std::decay_t<T>, magic_factory>::value>
>
magic_factory( T&& t ) {
ptr = {new std::decay_t<T> >(std::forward<T>(t)),
[](void* ptr){
delete static_cast< std::decay_t<T>* >(ptr);
}
};
// 64 lines like this:
std::get<factory_ptr< Base< std::int8_t, std::int8_t, std::int8_t > >>( table )
= +[](void* pvoid, tag_t<Base< std::int8_t, std::int8_t, std::int8_t >> tag, char const* param)->std::unique_ptr<Base< std::int8_t, std::int8_t, std::int8_t >>
{
auto*pt = static_cast<std::decay_t<T>*>(pvoid);
return (pt)(tag, param);
};
}
};
版本那样复制可调用对象的状态N次。
另一种方法是使用my type erasing type erasure解决方案。
我们编写了一组模板tuple
来执行标记技巧来创建std::function
对象。
然后,我们在每个any_method
上创建一个Base<A,B,C>
。
然后我们继承它并将您的super_any
包装到那些any_method
。
这可能与上面的手动方法一样有效。
make
现在我们的工厂是:
any_method
template<class T0, class T1, class T2>
auto make_base = make_any_method<std::unique_ptr<Base<T0, T1, T2>>(char const* name)>(
[](auto* p, char const* name)
{
return p->make<T0, T1, T2>( name );
}
);
template<class T0, class T1, class T2>
using base_maker = decltype(make_base);
可以存储指向实现super_any< base_maker<std::int8_t, std::int8_t, std::int8_t > > bob;
的类的指针,它们匹配bob
。
再添加64行并完成。
make<T0, T1, T2>
存储的类型根本不需要具有公共基类。它不使用C ++继承来实现多态,而是使用手动类型擦除。
要打电话给你,
int8_t, int8_t, int8_t
自然地将更多类型传递给bob
,并支持更多类型的auto r = (bob->*make_base<std::int8_t, std::int8_t, std::int8_t>)("hello");
。