避免虚拟模板功能

时间:2017-01-12 19:14:33

标签: c++ templates factory virtual-method

有人可以在下面的代码中提出一种避免虚拟模板功能的技术吗?我已经阅读了其他几篇文章,我不知道如何将这些解决方案应用于此案例。

我正在构建一个包含模板化类层次结构的库。我想创建一个“工厂”函数数组,可用于按名称实例化派生类(例如,基于命令行参数)。

如果可能的话,我希望每个派生类能够在它自己的.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");


}

1 个答案:

答案 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::functions

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" );

bobunique_ptrBase<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");