Reputation: 2436
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
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
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
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