С++ Метапрограммирование: генерация последовательности байтов на основе ввода типа/значения?

51
4

Так что это может быть один из тех вопросов, которые "очевидно просты" или "явно невозможны".

Но представьте себе простой буферный протокол, в котором данным предшествует байт, указывающий тип. Таким образом, у вас будет 04 00 для ложного bool и 02 00 00 00 01 для uint32_t со значением 1.

Можно ли сериализовать данные во время компиляции, чтобы Serialize(false, true, uint32_t(3)); возвращает последовательность вроде 04 00 04 01 02 00 00 00 03?

Если это так, можно ли создать такие подготовленные операторы, которые будут иметь нули в последовательности, которые могут быть заполнены во время выполнения? А также, возможно, даже выполняю базовое модульное тестирование, такое как static_assert(Deserialize<bool, bool>("\x04\x01\x04\x00")[0] == true, "Error");

// e.g something like this.
template<typename T> constexpr uint8_t Prefix()
{
if constexpr (std::is_same_v<T, bool>) return 0x04;
if constexpr (std::is_same_v<T, uint32_t>) return 0x02;
return 0x00;
};
template <typename T> ctString Write(T Value)
{
ctString Temp{};
Temp += Write(Prefix<T>());
Temp += /* something sizeof(Value) */;
return Temp;
};
template <typename... Args> ctString Serialize(std::tuple<Args...> Input)
{
ctString Temp{};
std::apply([&](auto Argument) { Temp += Write(Arguement); }, Input);
return Temp;
};

constexpr auto Message = Serialize(false, true, uint32_t(3));

спросил(а) 2018-12-11T19:37:00+03:00 1 год, 3 месяца назад
1
Решение
63

Что-то вроде этого:

template <typename... Args>
constexpr auto serialize(Args... args) {
// better not pass in std::string or something...
static_assert((std::is_trivially_copyable_v<Args> && ...));

// each size better fit in a byte
static_assert((sizeof(Args) < 256 && ...));

std::array<char,
(sizeof...(Args) + // one byte for each arg
(sizeof(Args) + ...) // sum of the sizes of the args
)> buffer;
char* p = buffer.data();
auto fill = [](char* p, auto arg) { /* ... */ };
(p = fill(p, args), ...);
return buffer;
}

Сложная часть заключается в написании fill, что вы можете реализовать ее в constexpr - поскольку вы не можете использовать memcpy или размещение нового для записи аргумента в буфер. Вы должны вручную что-то делать - это означает, что вы должны вручную управлять порядком байтов и всем таким барахлом. Простое направление:

auto fill = [](char* p, auto arg) {
*p++ = sizeof(arg);
for (int i = 0; i < sizeof(arg); ++i) {
*p++ = arg & 0xff;
arg >>= 8;
}
return p;
};

ответил(а) 2018-12-11T20:00:00+03:00 1 год, 3 месяца назад
-4

Просто для удовольствия, я предлагаю шаблон-метапрограммирующую версию, которая работает только во время компиляции, и сохраняю сериализацию в типе std::integer_sequence<char,...> (intead of char вы можете использовать unsigned char или std::uint8_t или другой тип, очевидно).

Таким образом, сериализация завершается передачей значений, которые вы хотите сериализовать в качестве аргументов шаблона, получая тип (это работает только начиная с С++ 17, потому что используются auto типы значений шаблона)

using t1 = Serialize_t<false, true, std::uint32_t{3u}>;

и десериализация возвращают constexpr std::tuple<Ts...> (где Ts... - соответствующие типы)


// d, in this case, is a std::tuple<bool, bool, std::uint32_t>
constexpr auto d { Deserialize_v<t1> };

static_assert( std::get<0>(d) == false );
static_assert( std::get<1>(d) == true );
static_assert( std::get<2>(d) == std::uint32_t{3u} );

Ниже приведен пример полной компиляции (С++ 17).

#include <memory>
#include <iostream>
#include <functional>

template <int I, auto V, char ... Cs>
struct IntChs : public IntChs<I-1, (V >> 8), char(V & 0xff), Cs...>
{ };

template <auto V, char ... Cs>
struct IntChs<0, V, Cs...>
{ using type = std::integer_sequence<char, Cs...>; };

template <int I, auto V>
using IntChs_t = typename IntChs<I, V>::type;

template <typename, char ...>
struct ConcatChs;

template <char ... Cs0, char ... Cs1>
struct ConcatChs<std::integer_sequence<char, Cs0...>, Cs1...>
{ using type = std::integer_sequence<char, Cs1..., Cs0...>; };

template <typename T, T V, char ... Cs>
struct SerVal;

// std::uint32_t case
template <std::uint32_t V, char ... Cs>
struct SerVal<std::uint32_t, V, Cs...>
: public ConcatChs<IntChs_t<4, V>, Cs..., '\x02'>
{ };

// bool case
template <bool V, char ... Cs>
struct SerVal<bool, V, Cs...>
: public ConcatChs<IntChs_t<1, int(V)>, Cs..., '\x04'>
{ };

// ******************************** //
// other serialization cases to add //
// ******************************** //

template <auto V, char ... Cs>
struct ConcatSer : public SerVal<decltype(V), V, Cs...>
{ };

template <auto V, char ... Cs>
using ConcatSer_t = typename ConcatSer<V, Cs...>::type;

template <typename, auto ...>
struct Serialize;

template <char ... Cs, auto V0, auto ... Vs>
struct Serialize<std::integer_sequence<char, Cs...>, V0, Vs...>
: public Serialize<ConcatSer_t<V0, Cs...>, Vs...>
{ };

template <typename T>
struct Serialize<T>
{ using type = T; };

template <auto ... Vs>
using Serialize_t = typename Serialize<std::integer_sequence<char>, Vs...>::type;

template <typename T, char ... Cs>
constexpr T Val ()
{
T ret{};

((ret <<= 8, ret += T(Cs)), ...);

return ret;
}

template <typename, auto...>
struct Deserialize;

// bool case
template <char C0, char ... Cs, auto ... Vs>
struct Deserialize<std::integer_sequence<char, '\x04', C0, Cs...>, Vs...>
: public Deserialize<std::integer_sequence<char, Cs...>,
Vs..., Val<bool, C0>()>
{ };

// std::uint32_t case
template <char C0, char C1, char C2, char C3, char ... Cs, auto ... Vs>
struct Deserialize<std::integer_sequence<char, '\x02', C0, C1, C2, C3, Cs...>,
Vs...>
: public Deserialize<std::integer_sequence<char, Cs...>,
Vs..., Val<std::uint32_t, C0, C1, C2, C3>()>
{ };

// ********************************** //
// other deserialization cases to add //
// ********************************** //

// final case: the tuple
template <auto ... Vs>
struct Deserialize<std::integer_sequence<char>, Vs...>
{ static constexpr auto value = std::make_tuple(Vs...); };

template <typename T>
constexpr auto Deserialize_v = Deserialize<T>::value;

int main()
{
using t1 = Serialize_t<false, true, std::uint32_t{3u}>;
using t2 = std::integer_sequence<char,
'\x04', '\x00', '\x04', '\x01', '\x02', '\x00', '\x00', '\x00', '\x03'>;

static_assert( std::is_same_v<t1, t2> );

constexpr auto d { Deserialize_v<t1> };

static_assert( std::get<0>(d) == false );
static_assert( std::get<1>(d) == true );
static_assert( std::get<2>(d) == std::uint32_t{3u} );
}

Этот код работает для архитектур с прямым порядком байтов и с прямым порядком байтов, но с большим ограничением: требуется, чтобы число бит в char 8.

ответил(а) 2018-12-11T22:29:00+03:00 1 год, 3 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема