【问题标题】:Optimize template replacement of a switch优化交换机模板替换
【发布时间】:2014-06-19 06:17:58
【问题描述】:

我的一个项目中有很多自定义数据类型,它们都共享一个公共基类。

我的数据(来自数据库)的数据类型由基类的枚举来区分。我的架构允许使用派生类专门化特定的数据类型,或者它可以由基类处理。

当我构造一个特定的数据类型时,我通常会直接调用构造函数:

Special_Type_X a = Special_Type_X("34.34:fdfh-78");
a.getFoo();

有一些模板魔法也允许像这样构造它:

Type_Helper<Base_Type::special_type_x>::Type a =  Base_Type::construct<Base_Type::special_type_x>("34.34:fdfh-78");
a.getFoo();

对于枚举类型的某些值,可能没有专门化,所以

Type_Helper<Base_Type::non_specialized_type_1>::Type == Base_Type

当我从数据库中获取数据时,数据类型在编译时是未知的,因此有第三种方法来构造数据类型(来自 QVariant):

Base_Type a = Base_Type::construct(Base_type::whatever,"12.23@34io{3,3}");

但我当然希望调用正确的构造函数,所以该方法的实现过去看起来像:

switch(t) {
     case Base_Type::special_type_x:  
        return Base_Type::construct<Base_Type::special_type_x>(var);

     case Base_Type::non_specialized_type_1:  
        return Base_Type::construct<Base_Type::non_specialized_type_1>(var);              

     case Base_Type::whatever:  
        return Base_Type::construct<Base_Type::whatever>(var);     

     //.....
}

这段代码是重复的,因为基类也可以处理新类型(添加到枚举中),所以我想出了以下解决方案:

// Helper Template Method
template <Base_Type::type_enum bt_itr>
Base_Type construct_switch(const Base_Type::type_enum& bt, const QVariant& v)
{
  if(bt_itr==bt)
    return Base_Type::construct<bt_itr>(v);
  return construct_switch<(Base_Type::type_enum)(bt_itr+1)>(bt,v);
}

// Specialization for the last available (dummy type): num_types
template <>
Base_Type construct_switch<Base_Type::num_types>(const Base_Type::type_enum& bt, const QVariant&)
{
  qWarning() << "Type" << bt << "could not be constructed";
  return Base_Type(); // Creates an invalid Custom Type
}

我原来的switch语句被替换为:

return construct_switch<(Base_Type::type_enum)0>(t,var);

此解决方案按预期工作。

但是编译后的代码是不同的。虽然原始 switch 语句的复杂度为 O(1),但新方法的复杂度为 O(n)。生成的代码递归调用我的辅助方法,直到找到正确的条目。

为什么编译器不能正确优化这个?有没有更好的方法来解决这个问题?

类似的问题: Replacing switch statements when interfacing between templated and non-templated code

我应该提一下,我想避免使用C++11C++14 并坚持使用C++03

【问题讨论】:

    标签: c++ templates template-meta-programming


    【解决方案1】:

    这就是我所说的魔法开关问题——如何获取一个(范围的)运行时间值并将其转换为编译时间常数。

    抽象地说,你想生成这个 switch 语句:

    switch(n) {
      (case I from 0 to n-1: /* use I as a constant */)...
    }
    

    您可以使用参数包在 C++ 中生成与此类似的代码。

    我将从-替换样板开始:

    template<unsigned...> struct indexes {typedef indexes type;};
    template<unsigned max, unsigned... is> struct make_indexes: make_indexes<max-1, max-1, is...> {};
    template<unsigned... is> struct make_indexes<0, is...>:indexes<is...> {};
    template<unsigned max> using make_indexes_t = typename make_indexes<max>::type;
    

    现在我们可以轻松地创建一个从 0 到 n-1 的无符号整数的编译时序列。 make_indexes_t&lt;50&gt; 扩展为 indexes&lt;0,1,2,3, ... ,48, 49&gt; 版本在 O(1) 步骤中执行此操作,因为大多数(全部?)编译器使用内在函数实现 std::make_index_sequence。上面是线性的(在编译时——在运行时什么都不做)递归深度和二次编译时内存。这很糟糕,你可以在工作上做得更好(对数深度,线性内存),但是你有超过 100 种类型吗?如果没有,这就足够了。

    接下来,我们构建一个回调数组。因为我讨厌 C 遗留函数指针语法,所以我会抛出一些毫无意义的样板来隐藏它:

    template<typename T> using type = T; // pointless boilerplate that hides C style function syntax
    
    template<unsigned... Is>
    Base_Type construct_runtime_helper( indexes<Is...>, Base_Type::type_enum e, QVariant const& v ) {
      // array of pointers to functions:  (note static, so created once)
      static type< Base_Type(const QVariant&) >* const constructor_array[] = {
        (&Base_Type::construct<Is>)...
      };
      // find the eth entry, and call it:
      return constructor_array[ unsigned(e) ](v);
    }
    Base_Type construct_runtime_helper( Base_Type::type_enum e, QVariant const& v ) {
      return construct_runtime_helper( make_indexes_t< Base_Type::num_types >(), e, v );
    }
    

    鲍勃是你的叔叔1。用于调度的 O(1) 数组查找(使用 O(n) 设置,理论上可以在您的可执行文件启动之前完成)。


    1 “鲍勃是你的叔叔”是英联邦的一句谚语,粗略地说“一切都已完成并正常工作”。

    【讨论】:

    • 这是一个不错的解决方案!使用 c++03 时我有哪些选择?
    • @dreamcooled 升级编译器,使用链式-if 语句,使用工具生成代码,复制意大利面,使用预处理器宏生成代码。
    • 编译器升级。完美运行。谢谢。
    • 回复:“鲍勃是你的叔叔”——这是en.wikipedia.org/wiki/Robert_Cecil_Martin 的引用吗?谢谢。
    【解决方案2】:

    所有函数都是内联的吗?我希望有一个合理的编译器将if 树优化为switch,但前提是ifs 在同一个函数中。为了可移植性,您可能不想依赖它。

    您可以通过间接函数调用获得 O(1),方法是让 construct_switch 使用执行构造的 lambda 函数填充 std::vector&lt;std::function&lt;Base_Type(const QVariant&amp;)&gt;&gt;,然后调度它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-09-30
      • 1970-01-01
      • 1970-01-01
      • 2021-01-01
      • 2021-07-31
      • 1970-01-01
      • 2016-11-10
      • 1970-01-01
      相关资源
      最近更新 更多