C++ 元编程:根据 type/value 输入生成字节序列?

C++ Metaprogramming: Generating a byte sequence based on type/value input?

所以这可能是“这显然很容易”或“显然不可能”的问题之一..

但想象一个简单的缓冲协议,其中数据以指示类型的字节为前缀。因此,您将 04 00 用于 false bool,而 02 00 00 00 01 用于值为 1 的 uint32_t。

是否可以在编译时序列化数据,以便 Serialize(false, true, uint32_t(3)); returns 像 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(Argument); }, Input);
    return Temp;
};

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

像这样:

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 或 placement new 将 arg 写入缓冲。你必须手动做事 - 这意味着你必须手动管理字节顺序和所有垃圾。简单的方向是:

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

只是为了好玩,我建议使用编译时 的模板元编程版本,并将序列化存储在 std::integer_sequence<char, ...> 类型中(而不是 char 你可以使用 unsigned charstd::uint8_t 或其他类型,显然)。

因此序列化已完成,将您想要序列化的值作为模板参数传递并获取类型(这仅适用于从 C++17 开始,因为使用 auto 模板值类型)

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

和反序列化return一个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} );

以下为完整编译(C++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。