【问题标题】:C++ Select Template Non-Type Parameters from Compile-Time Known Set/Enum at Run-TimeC++ 在运行时从编译时已知集/枚举中选择模板非类型参数
【发布时间】:2017-03-27 12:08:27
【问题描述】:

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


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

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

【问题讨论】:

  • 你不能在运行时做任何与模板相关的事情,这是一个仅在编译时的功能。
  • 你可以,但它需要一个非常大的开关。编辑:取决于可能的模板实例化的数量,它可以用黑魔法(宏)来完成,但你可能想找到另一个解决方案
  • 但是我在编译时知道所有可能的值,所以编译器必须有办法从集合中提取这些可能的值并为排列创建类?还是在预处理器宏中执行此操作的好方法?
  • 好的,传入递归宏奥术
  • 如果subab 中的任何 在运行时被初始化或设置,那么真的没有办法将它们用于模板。我能想到的唯一可行的解​​决方案不是条件语句(ifswitch)是使用映射或类似的东西来实例化所有类。或者重新设计。

标签: c++ templates factory-pattern


【解决方案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) {
            ...
        }
}

【讨论】:

  • 嗯,绝对是黑魔法,我喜欢。这正是我想要的,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 2017-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多