【问题标题】:C++ variadic templates with tuples带有元组的 C++ 可变参数模板
【发布时间】:2014-03-17 14:04:06
【问题描述】:

我想编写一个函数来从二进制缓冲区中提取一些数据(假设数据是按顺序存储的)。该函数返回数据和提取数据后的指针,如下所示

std::tuple<const unsigned char *, int, double, float> data = Extract<int, double, float>(p_buffer);

它从p_buffer 中提取intdoublefloat 数字,data 的第一个值指示下一次提取工作的开始位置。

我试着写这样的东西。

#include <tuple>

typedef unsigned char byte;

template<class TFirst, class... TRest>
struct Extractor
{
  static std::tuple<const byte *, TFirst, TRest...> Extract(const byte *p_current)
  {
    TFirst first_value;
    TRest... rest_values; // Not working.

    std::tie(p_current, first_value) = Extractor<TFirst>::Extract(p_current);
    std::tie(p_current, rest_values...) = Extractor<TRest...>::Extract(p_current);

    return std::make_tuple(p_current, first_value, rest_values...);
  }
};

template<class T>
struct Extractor<T>
{
  static std::tuple<const byte *, T> Extract(const byte *p_current)
  {
    return std::make_tuple(p_current + sizeof(T), *reinterpret_cast<const T *>(p_current));
  }
};

它无法编译,因为“参数包无法在此上下文中展开”。我听说函数模板不能部分特化,所以我使用结构。如何让它发挥作用?

【问题讨论】:

  • 请不要说“不行”。
  • 使返回值成为char* 的命名类型,后跟一个元组,而不是一个大元组。或两层元组。在其他地方进行类似的更改以将类型保留在元组 esp 包中。而且应该很容易。

标签: c++ templates c++11 tuples variadic-templates


【解决方案1】:

这是一个纯 C++11 的解决方案:

#include <tuple>
#include <type_traits>

typedef unsigned char byte;

template <class Type>
void ExtractValue(const byte*& p_current, Type& value)
{
    value = *reinterpret_cast<const Type*>(p_current);
    p_current += sizeof(Type);
}

template <size_t index, class... Types>
typename std::enable_if<index == sizeof...(Types)>::type
ExtractImpl(const byte*& p_current, std::tuple<Types...>& values)
{}

template <size_t index, class... Types>
typename std::enable_if<(index < sizeof...(Types))>::type
ExtractImpl(const byte*& p_current, std::tuple<Types...>& values)
{
    ExtractValue(p_current, std::get<index>(values));
    ExtractImpl<index + 1>(p_current, values);
}

template <class... Types>
std::tuple<Types...> Extract(const byte *p_current)
{
    std::tuple<Types...> values;

    ExtractImpl<0>(p_current, values);

    return values;
}

此解决方案不会将p_current 添加到返回的元组中,但您可以轻松修复它。

【讨论】:

    【解决方案2】:

    我发现这样做要简单得多,根本没有模板递归 (live example):

    template<typename T>
    T extract(const byte *&p_current)
    {
       const byte *p = p_current;
       p_current += sizeof(T);
       return *reinterpret_cast<const T*>(p);
    }
    
    template<typename... T>
    std::tuple<T...> Extract(const byte *&p_current)
    {
       return std::tuple<T...>{extract<T>(p_current)...};
    }
    

    不幸的是,由于bug 的持续存在,gcc 以相反的顺序(从右到左)评估tuple 的列表初始化的参数,因此您将在实时示例中看到它有效仅在铿锵声中正确。但是,如果在调用Extract 之前翻转参数列表T...,仍然可以应用此解决方案,仅用于gcc。我更喜欢保持简单并等到错误修复。

    【讨论】:

      【解决方案3】:
      TRest... rest_values; // Not working.
      

      这是无效的,您不能声明 TRest... 类型的变量,因为那不是类型(它是一组类型)。

      Yakk 上面的评论是一个更干净的解决方案,但如果你真的只想使用元组,你可以这样做:

      std::tuple<TRest...> rest_values;
      

      然后修改以下代码以使用它。

      你不能轻易地将 std::tie 与元组元素一起使用(你需要 C++14 的 apply() 函数),所以我会这样做:

        static std::tuple<const byte *, TFirst, TRest...> Extract(const byte *p_current)
        {
          TFirst first_value;
          std::tuple<const char*&, TRest...> rest_values{ p_current, TRest{}... };
      
          std::tie(p_current, first_value) = Extractor<TFirst>::Extract(p_current);
          rest_values = Extractor<TRest...>::Extract(p_current);
      
          return std::make_tuple(p_current, first_value, cdr(rest_values));
        }
      

      其中cdr() 是 Lisp CDR 操作,它返回列表的第一个元素以外的所有内容。

      【讨论】:

      • Lisp?谁说的“Lisp”?
      • @Constructor,我做到了。 Look it up,众所周知的功能
      • 这是一个众所周知的函数,但它不是 C++ 函数。
      • @Constructor,不过写起来很简单,这里就不费心去做了。我的回答解释了问题中的编译器错误,如果您不喜欢建议的替代方案,请提出更好的建议。
      • 在 C++ 中,您不能简单地将元组转换为参数列表。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-08
      • 2018-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多