EFanZh
EFanZh

Reputation: 2436

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?

Upvotes: 1

Views: 3641

Answers (3)

iavr
iavr

Reputation: 7647

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.

Upvotes: 2

Constructor
Constructor

Reputation: 7491

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.

Upvotes: 3

Jonathan Wakely
Jonathan Wakely

Reputation: 171501

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.

Upvotes: 1

Related Questions