简体   繁体   中英

C++ variadic templates with tuples

I want to write a function to extract some data from a binary buffer (assume the data is sequentially stored). The function returns data and the pointer after the extracted data, like this

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

which extracts an int , a double and a float number from p_buffer , and the first value of data indicates where to begin the next extraction work.

I tried to write something like this.

#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));
  }
};

It doesn't compile because "parameter pack cannot be expanded in this context". I hear that function templates can't be partially specialized, so I use structs. How to make it work?

Here is a pure C++11 solution:

#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;
}

This solution does not add p_current to the returned tuple, but you may easily fix it.

I find it much simpler to go like this, with no template recursion at all ( 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)...};
}

Unfortunately, due to a persisting bug , gcc evaluates arguments of the tuple 's list-initialization in the opposite order (right-to-left), so you'll see in the live example that it works correctly only in clang. But it's still possible to apply this solution if one flips parameter list T... before calling Extract , just for gcc. I prefer to keep it simple and wait until the bug is fixed.

TRest... rest_values; // Not working.

This is not valid, you can't declare a variable of type TRest... because that's not a type (it's a pack of types).

Yakk's comment above is a cleaner solution, but if you really want to only use tuples you can do:

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

and then modify the following code to work with that.

You cannot easily use std::tie with the tuple elements (you would need C++14's apply() function) so I would do this instead:

  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));
  }

Where cdr() is the Lisp CDR operation, which returns everything but the first element of a list.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM