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 char
或 std::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。
所以这可能是“这显然很容易”或“显然不可能”的问题之一..
但想象一个简单的缓冲协议,其中数据以指示类型的字节为前缀。因此,您将 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 char
或 std::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。