在运行时选择适当的专用模板

时间:2015-05-11 13:49:18

标签: c++ templates c++11 template-specialization

我正在使用来自第三方库的类,看起来像

...
if (event.getCode() == KeyCode.ENTER) {
     ...
     event.consume(); // Consume Event
}
...

我想在运行时根据条件使用此类的部分特化,例如

template <typename A = DefaultT, typename B = DefaultT, typename C = DefaultT, typename D = DefaultT, typename E = DefaultT, typename F = DefaultT>
class Vertex {};

我想有条件地选择我想要专门化的模板参数。 我不确定是否存在这个问题的术语,我怀疑它是多重调度的一种风格。

一种简单的方法是编写15(2 ^ 4 -1)个类,如

class MyA {};
class MyB {};
class MyC {};
class MyD {};
bool useA, useB, useC, useD; //these booleans change at runtime
// I want to specialize Vertex depending on the above booleans
// The below line shouldn't compile, this is just to give an idea
typedef typename Vertex <useA ? MyA : DefaultT,  useB ? MyB : DefaultT,  
                         useC ? MyC : DefaultT,  useD ? MyD : DefaultT> MyVertex;

问题变得更加复杂,因为我必须使用网格&#39;使用专门的顶点类的类

typedef typename Vertex <MyA> MyVertexWithA;
typedef typename Vertex <DefaultT, MyB> MyVertexWithB;
typedef typename Vertex <MyA, MyB> MyVertexWithAB; //and so on...until
typedef typename Vertex <MyA, MyB, MyC, MyD> MyVertexWithABCD;

现在,如果我沿着编写15个类的路径走下去,那么我将不得不为每个不同的网格类型再写15行。并且在使用Mesh类的地方变得越来越复杂。

我坚信这必须由我或编译器来完成。我的问题:

  1. 我想知道是否有办法让编译器为我做这项工作?
  2. C ++ 11是否有更好的机制来处理这种情况? 感谢。

2 个答案:

答案 0 :(得分:6)

答案是否定的。如果条件在运行时发生变化,那么你不能根据这些条件专门化/实例化模板,它们需要是常量表达式(毕竟,编译器在编译时执行此工作,在程序开始运行之前 ,所以使用运行时表达式是禁止的。如果在编译时确定它们,那么您可以将constexpr技巧与std::conditional结合使用。

正如@Karloy Horvath所提到的,你也可以做一些叫做标签调度的事情,类似于下面的例子:

#include <iostream>

struct tag_yes{};
struct tag_no{};

template <typename T> void f_tag();

template <> 
void f_tag<tag_yes>() // specializations 
{
    std::cout << "YES" << std::endl;
}

template <> 
void f_tag<tag_no>()
{
    std::cout << "NO" << std::endl;
}

void f(bool condition)
{
    if(condition)
        f_tag<tag_yes>(); // call the YES specialization
    else
        f_tag<tag_no>(); // call the NO specialization
}

int main()
{
    bool condition = true;
    f(condition); // dispatch to the "right" template function
}

或者,您甚至可以推断标签(标准C ++算法如何与各种迭代器类型一起使用)

#include <iostream>
#include <type_traits>

struct Foo
{
    using tag = std::true_type;
};

struct Bar
{
    using tag = std::false_type;
};

template <typename T> void f_tag();

template <> 
void f_tag<std::true_type>() // specializations 
{
    std::cout << "YES" << std::endl;
}

template <> 
void f_tag<std::false_type>()
{
    std::cout << "NO" << std::endl;
}

template <typename T>
void f(const T&)
{
        f_tag<typename T::tag>(); // dispatch the call
}

int main()
{
    Foo foo;
    Bar bar;

    f(foo); // dispatch to f_tag<std::false_type>
    f(bar); // dispatch to f_tag<std::true_type>
}

但是,在您的情况下,多态性可能是通过工厂方法和公共虚拟接口的方式。

答案 1 :(得分:2)

“运行时模板专业化”是绝对禁止的。编译器在编译期间处理模板,生成编译为二进制代码的类。

如果编译器没有生成它,你根本就没有适当的二进制代码来调用,因此你不能在运行时实例化这样的类。

您需要使用运行时多态性。无论是内置的虚拟机制还是简单的插件。

例如,您的Vertex具有属性InterfaceA,并且在构造函数中,默认情况下您使用DefaultA来实现/派生自InterfaceA。但是你可以传递一些CustomA,它也来自InterfaceA。没有解决这个问题。根据在运行时完成的选择,您无法获得编译时机制。

编辑: 如果您拥有所有类并且需要在运行时选择适当的版本。那么Mesh使用的顶点中有一些共同的接口是合乎逻辑的,因此它们应该来自一个公共类。

因此,您需要创建一个工厂来创建一个适当类型的对象,并将其转换为VertexInterface并将其返回。

VertexInterface makeVertex(bool useA, bool useB){
  if(useA && useB) return VertexInterface<MyA, MyB>();
  if(useA && !useB) return VertexInterface<MyA, DefaultT>();
  if(!useA && useB) return VertexInterface<DefaultT, MyB>();

  // default case
  return VertexInterface<DefaultT, DefaultT>();
}

您需要工厂处理所有(未)支持的案例。不幸的是,调度需要手动完成,这是模板和运行时之间的桥梁。