C++ Select 来自编译时的模板非类型参数在 运行 时已知 Set/Enum

C++ Select Template Non-Type Parameters from Compile-Time Known Set/Enum at Run-Time

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


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

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

我有一个基准测试工具,可以从数据库中提取不同的基准配置(subclass 到 运行、模板参数和 arguments/workloads)。模板参数是最终集(a in {256, 512, 1024, 2048, 4096, 8192, 16384}b in {1, 2, 3})。

我希望能够使用正确的模板参数将 Base 对象实例化为子 class,而不必 if/else if 所有 possibilities/combinations.在 C++ 中是否有一种干净的方法可以使用枚举、数组甚至预处理器宏来执行此操作?

一个冗长的方法是有很多 if-else 语句,但我想要一个更简洁的解决方案,以便尽可能地从枚举、集合或数组中提取值。肯定有一种生成组合的预处理器方法,或者一种从枚举中 select 的方法(因此编译器创建所有枚举组合的 classes)?

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 */);

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

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

//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

并恶搞由宏为您生成的丑陋开关。 这可以打开任何运行时和 return 任何编译时间(我的示例打开 n 枚举以作为类型转发到(可变)模板化方法)

生成的代码如下所示:

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