C ++在运行时从编译时已知集/枚举中选择模板非类型参数

时间:2017-03-27 12:08:28

标签: c++ templates factory-pattern

TL; DR:寻找预处理器宏来为模板参数的预定义集/枚举的所有组合生成所有if/else-if/else-error语句。

我有一个抽象类(SubA<int a>)的3个子类(SubB<int a>SubC<int a, int b>Base),所以我不能初始化抽象类但是可以初始化子类。这些子类还有一个或两个非类型模板参数。

class Base {...};  // abstract
class SubA<int a> : public Base {...};
class SubB<int a> : public Base {...};
class SubC<int a, int b> : public Base {...};

我有一个基准测试工具,它从数据库(运行的子类,模板参数和参数/工作负载)中提取不同的基准配置。模板参数是权威集(a in {256, 512, 1024, 2048, 4096, 8192, 16384}b in {1, 2, 3})。

我希望能够将Base对象实例化为具有正确模板参数的子类,而不必if/else if所有可能性/组合。有没有一种干净的方法在C ++中使用枚举,数组甚至预处理器宏来做到这一点?

一个冗长的方法是拥有许多if-else语句,但我想要一个更清晰的解决方案,如果可能的话,将值从枚举,集合或数组中拉出来。当然,有一种预处理器生成组合的方式,或者一种从枚举中选择的方法(因此编译器会创建所有枚举组合的类)?

Base *base = nullptr;

if (sub == "SubA") {
  if (a == 512) {
    if (b == 1) {
      base = new SubA<512, 1>();
    } else if (b == 2) {
      base = new SubA<512, 2>();
    } else if (b == 3) {
      base = new SubA<512, 3>();
    }
  } else if (a == 1024) {
    // ...
  }
} else if (sub == "SubB") {
  // ...
} else if (sub == "SubC") {
  // ...
}

if (base == nullptr) {
  throw std::exception();
}

作为进一步的解释,这里是用JS编写的等效解决方案:

class Base = {...};
function SubAFactory(a, b) = {return class SubA {... definition ...}};
function SubBFactory(a, b) = {return class SubB {... definition ...}};
function SubCFactory(a, b) = {return class SubC {... definition ...}};

const SubFactories = {
  SubA: SubAFactory,
  SubB: SubBFactory,
  SubC: SubCFactory
};

function BaseFactory(sub, a, b) {
  // NOTE: an if-else between subclasses would also be fine
  //       as long as the template args are "dynamic".
  return SubFactories[sub](a, b);
}

// retrieved from db at runtime, a and b values will
// always be part of a finite set known at compile time
const dbResult = {sub: 'SubA', a: 2048, b: 2};  
const sub = dbResult.sub;
const a = dbResult.a;
const b = dbResult.b;

const base = new BaseFactory(sub, a, b)(/* class constructor args */);

1 个答案:

答案 0 :(得分:1)

首先让我告诉你,如果你有另一种方法来解决你的问题,那就去做吧。然后,如果模板实例化的目标集是有限的而不是太大,则有一种方法可以将切换分解。方法如下:

首先,我们需要能够创建对宏的引用并扩展它们,以便我们可以在创建工厂后立即编写一些通用的东西。这些宏是:

//force another dereferencing cycle
# define EXPAND(...) __VA_ARGS__
//black magic
# define EMPTY(...)

//dereference a macro reference on expansion
# define DEFER(...) __VA_ARGS__ EMPTY()

然后是真正的部分:构建一个开关,使代码在每个运行时案例中转发编译时类型:

#define makeCase(_value, appendTo, ...) case _value: \
            DEFER(appendTo)()(__VA_ARGS__, _value) \
            break; 

#define makeRuntimeSwitch(_runtimeVal, appendTo, ...) switch( _runtimeVal) \
    { \
        makeCase(1, appendTo, __VA_ARGS__) \
        makeCase(2, appendTo, __VA_ARGS__) \
        makeCase(3, appendTo, __VA_ARGS__) \
        makeCase(4, appendTo, __VA_ARGS__) \
        makeCase(5, appendTo, __VA_ARGS__) \ 
    }

这会将我们的模板参数附加到 VA_ARGS ,直到我们拥有它们并且能够将它们与另一个宏一起使用:

#define makeRuntimeConsume(_p1, _p2) return new templatedStuff<_p1, _p2>();

现在我们所要做的就是创建对宏的引用并使用它们来构建我们的工厂:

#define makeRuntimeConsumeId() makeRuntimeConsume
#define makeRuntimeSwitchId() makeRuntimeSwitch
baseStuff* makeStuff(int a, int b)
{
    EXPAND(EXPAND(makeRuntimeSwitch( a, makeRuntimeSwitchId, b, makeRuntimeConsumeId)));
}
//undef all macro, you don't need them anymore

和恶搞为你生成的丑陋开关。 这可以打开任何运行时并返回任何编译时间(我的示例打开n枚举以将类型转发为(可变参数)模板化方法)

生成的代码如下所示:

switch (a) {
    case 1:
        switch (b) {
            case 1:
                return new templatedStuff<1, 1>();
            case 2:
            ...
        }
    case 2:
        switch (b) {
            ...
        }
}