可能的模板方案

时间:2015-10-15 07:43:38

标签: c++ templates c++11 c++14

函数的参数是特定类型的id。该函数将T中提供的值编码为标记对应的类型的值,因此我想完成以下操作。是否有任何简洁的模板技巧可以使这个工作?

template<int tag_id>
struct tag_traits;

template<>
struct tag_traits<1>
{
    typedef char type;
};

template<>
struct tag_traits<2>
{
    typedef int type;
};

template<>
struct tag_traits<3>
{
    typedef double type;
};

class test {
public:
    test()
    {}

    template <typename T>
    void add_field(int tag, T value)
    {
        using t = typename tag_traits<tag>::type;
        // more...
    }      
};

int main()
{
   test t;
   t.add_field(1, "");
   t.add_field(2, "");
}

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:3)

template <typename T>
void add_field(int tag, T value)
{
    using t = typename tag_traits<tag>::type;
    // more...
}

tag_traits<tag>的参数必须是编译时常量,在这种情况下不是。添加const也无法解决问题。

解决方案是将tag移动为模板参数。

template <int tag, typename T>
void add_field(T value)
{
    using t = typename tag_traits<tag>::type;
    // more...
}

另一个解决方案虽然稍微复杂一点,但是会使用标签调度技术 - 但同样,&#34; dispatch&#34;将是一个编译时计算。

如果要求获取运行时类型,那么模板可能根本就不是答案,因为模板被绑定到编译时代码生成。在这种情况下,变体和某种形式的访问或更传统的多态可能更合适。

答案 1 :(得分:3)

模板参数必须在编译时知道。如果您可以更改函数以将tag作为模板参数,则它将起作用:

template <int tag, typename T>
void add_field(T value)
{
    using t = typename tag_traits<tag>::type;
    // more...
}

// ...

int main()
{
   test t;
   t.add_field<1>("");
   t.add_field<2>("");
}

如果这不是一个选项,那么您需要一个switch或类似的构造:

template <typename T>
void add_field(int tag, T value)
{
  switch (tag) {
    case 1:
    {
      using t = typename tag_traits<1>::type;
      // more...
      break;
    }
    case 2:
    {
      using t = typename tag_traits<2>::type;
      // more...
      break;
    }
  }
}

问题是所有案例必须在语法和语义上对 T的所有值有效。如果这也不是一个选项,则需要具有辅助特征的繁重模板机器:

template <int tag, class T>
struct is_acceptable : std::false_type
{};

template <int tag>
struct is_acceptable<tag, typename tag_traits<tag>::type> : std::true_type
{};

// Add any other specialisations as applicable

// ...
template <typename T>
void add_field(int tag, T value)
{
  switch (tag) {
    case 1:
    {
      add_field_helper<1, T>::call(value);
      break;
    }
    case 2:
    {
      add_field_helper<2, T>::call(value);
      break;
    }
  }
}

template <int tag, class T, bool acceptable = is_acceptable<tag, T>::value>
struct add_field_helper
{
  static void call(T) {}
};

template <int tag, class T>
struct add_field_helper<tag, T, true>
{
  static void call(T value)
  {
    using t = typename tag_traits<tag>::type;
    // more...
  }
};

call false值的空is_acceptable仅存在,以防止错误实例化导致编译错误。假设您从未使用无效的add_field - tag组合来调用T,那么它将永远不会在运行时被调用。

如果有太多标签要实际列出,请使用Boost.Preprocessor生成它们。对于有效的代码值0 ... MAX_TAG - 1,可以使用以下代码:

#define ONE_CASE(z, tag, unused) \
  case tag: add_field_helper<tag, T>::call(value);  break;

template <typename T>
void add_field(int tag, T value)
{
  switch (tag) {
    BOOST_PP_REPEAT(MAX_TAG, ONE_CASE, %%)
  }
}

我没有使用通常传递给宏的数据参数,我通常使用句法无意义%%来表示。如果您愿意,可以在那里传递和使用任何东西。

如果Boost.Preprocessor对你来说太黑暗了,你也可以使用模板递归:

template <class T>
void add_field(int tag, T value)
{
  add_field_dispatcher<0, T>::call(tag, value);
}

template <int one_tag, class T>
struct add_field_dispatcher
{
  static void call(int tag, T value)
  {
    if (tag == one_tag) add_field_helper<one_tag, T>::call(value);
    else add_field_dispatcher<one_tag + 1, T>::call(tag, value);
  }
};

template <class T>
struct add_field_dispatcher<MAX_TAG, T>
{
  static void call(int tag, T value)
  {
    // Called with unsupported tag
  }
};