改进此 std::index_sequence 成语代码

Improve this std::index_sequence idiom code

提问人:Pablo 提问时间:11/2/2023 最后编辑:HolyBlackCatPablo 更新时间:11/3/2023 访问量:73

问:

我已经编写了一些序列化函数,这些函数能够计算大小,写入内存缓冲区并从任何结构/类的缓冲区中读取这些功能:包含特定的成员类型和“to_tuple”函数。

该方法的基础是返回包含对成员变量的所有引用的 a,并确保第一个是指示特定类型的枚举类变量。然后用于每个函数。std::tiestd::apply

为了从缓冲区读取,read 函数必须能够以不同的方式处理第一个成员(结构类型变量),所以我使用了 std::index_sequence 习惯用语来计算每个变量在元组中的位置,但代码对我来说看起来很笨拙和臃肿。

我必须限制 c++17,请不要提及由于 endianess 和这些事情而使用序列化是多么糟糕。std::memcpy

有没有更简单的方法来检测元组的第一个成员并在使用时以不同的方式处理它?std::apply

Coliru 链接: https://coliru.stacked-crooked.com/a/1c247e21bfef706b

#include <tuple>
#include <string>
#include <vector>
#include <iostream>
#include <ostream>
#include <cstring>
#include <utility>

enum class TYPES { TYPE_A, TYPE_B, TYPE_C, TYPE_D };

struct Structure
{
    int a1;
    int a2;
    int a3;
    bool b1;
    double c1;
    double c2;
    double c3;
    double c4;
    std::string d1;
    std::string d2;

    constexpr static inline auto type{ TYPES::TYPE_A };
    constexpr auto as_tuple() { return std::tie(type, a1, a2, a3, b1, c1, c2, c3, c4, d1, d2); }
};

// SIZE
template<class T>
constexpr size_t size_of(T&& val) { 
    using Type = std::decay_t<T>;
    if constexpr (!std::is_same_v<Type, std::string>) return sizeof(T);
    else                                              return sizeof(char) * val.size() + sizeof(char);
}

template<class... Ts>
constexpr size_t size(std::tuple<Ts...>&& tuple) {
    return std::apply([](auto&&... args) { return (size_of(args) + ...); }, tuple);
}

template<class T>
constexpr size_t size(T&& object) {
    return size(std::forward<T>(object).as_tuple());
}

// WRITE
template<class T>
constexpr void write_data(unsigned char* &data, T&& val) {
    using Type = std::decay_t<T>;
    if constexpr (!std::is_same_v<Type, std::string>) { std::memcpy(data, &val, sizeof(T)); data += sizeof(T); }
    else                                              { std::memcpy(data, val.c_str(), sizeof(char) * val.size()); data += sizeof(char) * val.size(); *data++ = 0; }
}

template<class... Ts>
constexpr void write(unsigned char* data, std::tuple<Ts...>&& tuple) {
    std::apply([&](auto&&... args) { (write_data(data, args), ...); }, tuple);
}

template<class T>
constexpr void write(unsigned char* data, T&& object) {
    write(data, std::forward<T>(object).as_tuple());
}

// READ -> std::index_sequence used, is there any other way to achieve this?
template<std::size_t I, class T>
constexpr void read_data(const unsigned char* &data, T&& val) {
    using Type = std::decay_t<T>;
    if      constexpr (I == 0)                             { static_assert(std::is_same_v<Type, TYPES>); data += sizeof(Type); }
    else if constexpr (!std::is_same_v<Type, std::string>) { std::memcpy(&val, data, sizeof(Type));      data += sizeof(Type); }
    else                                                   { val = std::string{ reinterpret_cast<const char*>(data) }; data += sizeof(char) * val.size() + sizeof(char); }
}

template<std::size_t... I, class... Ts>
constexpr void read(const unsigned char* data, std::tuple<Ts...>&& tuple, std::index_sequence<I...>) {
    static_assert(sizeof...(I) == sizeof...(Ts));
    std::apply([&](auto&&... args) { (read_data<I>(data, args), ...); }, tuple);
}

template<class T>
constexpr void read(const unsigned char* data, T&& object) {
    constexpr auto size = std::tuple_size_v<decltype(std::forward<T>(object).as_tuple())>;
    read(data, std::forward<T>(object).as_tuple(), std::make_index_sequence<size>{});
}

template<class... Ts>
void print_tuple(std::tuple<Ts...>&& tuple)
{
    std::apply([&](auto&&... args) { bool once{}; ((std::cout << (std::exchange(once, true) ? ", " : "") << args), ...) << "\n"; }, tuple);
}

template<class T>
void print_tuple(T&& object)
{
    print_tuple(std::forward<T>(object).as_tuple());
}

std::ostream& operator<< (const std::ostream& out, TYPES type)
{
    return std::cout << "Type_" << static_cast<int>(type);
}

int main()
{
    Structure original{ 1, 2, 3, true, 1.1, 2.2, 3.3, 4.4, "hello", "goodbye" };
    auto length = size(original);
    std::vector<unsigned char> buffer(length);
    write(buffer.data(), original);

    Structure copy;
    read(buffer.data(), copy);

    print_tuple(original);
    print_tuple(copy);

    return 0;
}
C++ C++17 标准图套 STDAPPLY

评论

1赞 Rerito 11/2/2023
也许改变你的方法?您可以拥有指向数据成员的所有成员指针的序列,而不是函数。由于 constexpr 成员,your 已经是类的静态属性,那么为什么不依赖它而不是将其作为转换的第一个成员返回呢?to_tuplestatic constexprTYPEStypeto_tuple
0赞 Pablo 11/3/2023
@Rerito :您想将您的评论分解为一个特定的答案,并带有代码演示您的方法吗?
1赞 Rerito 11/3/2023
我会努力的。我遇到的另一个想法是,你基本上有相同的模式重复:你有一个元素元组,你想单独对其每个成员应用特定的操作(所以这不是常规应用)。在帮助程序中抽象化它实际上使您能够通过在所述帮助程序中确定范围来摆脱样板索引序列代码(例如:coliru.stacked-crooked.com/a/34ce69d68358a6c8)

答:

2赞 Jarod42 11/3/2023 #1

只需在 apply 函数中添加第一个参数:

// READ
template<class T>
constexpr void read_data(const unsigned char* &data, T&& val) {
    using Type = std::decay_t<T>;
    if constexpr (!std::is_same_v<Type, std::string>) {
        std::memcpy(&val, data, sizeof(Type));
        data += sizeof(Type);
    } else {
        val = std::string{ reinterpret_cast<const char*>(data) };
        data += sizeof(char) * val.size() + sizeof(char);
    }
}

template<class T>
constexpr void read(const unsigned char* data, T&& object) {
    std::apply(
        [&](auto type, auto&&... args) {
//          ^^^^^^^^^
            data += sizeof(decltype(type)); // header
            (read_data(data, args), ...); },
        std::forward<T>(object).as_tuple());
}

演示

这假设 tuple_size 不是 0。

如果您必须处理 0 的情况,重载(来自 std::visit 示例)可能会有所帮助

// helper type for the visitor #4
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

然后

template<class T>
constexpr void read(const unsigned char* data, T&& object) {
    std::apply(
        overloaded(
            [](){ /* Empty pack code */ },
            [&](auto type, auto&&... args) { /*above code*/ }),
        std::forward<T>(object).as_tuple());
}

评论

0赞 Pablo 11/3/2023
@Jarod42:聪明的回答,我想知道为什么我没有早点意识到。非常感谢!