如何设计可序列化类以使任何非序列化属性都会导致编译时错误

How to design a serializable class such that any non-serialized attribute leads to a compile-time error?

本文关键字:序列化 属性 编译时错误 任何非      更新时间:2023-10-16

说您有以下代码:

class A {
  bool _attribute1;
};
// Arbitrarily using std::string, not the point of this question
std::string serialize(const A&);

现在,开发人员将新的bool _attribute2添加到class A,而忘记更新serialize功能,这会在运行时导致错误。(已经去过那里?)

有没有办法将此问题转变为编译时错误?由于C 不支持反射,我感到这是不可能的,但我可能会缺少任何东西。

如果您使用的是C 1Z,则可以使用结构化绑定:

struct S {
    bool b;
    //bool c; // causes error
};
int main() {
    S s;
    auto [x] = s;
    (void)x;
}

[live demo]

以下一个应与C 11一起工作。
确实有点棘手,它基于@samvarshavchik的评论:

#include<cstddef>
#include<functional>
template<std::size_t> struct Int { int i; };
template<std::size_t> struct Char { char c; };
template<std::size_t> struct Bool { bool c; };
template<typename, template<std::size_t> class...>
struct Base;
template<template<std::size_t> class... T, std::size_t... I>
struct Base<std::index_sequence<I...>, T...>: T<I>... {};
template<template<std::size_t> class... T>
struct Check final: Base<std::make_index_sequence<sizeof...(T)>, T...> {};
class A final {
    bool _attribute1;
    bool _attribute2;
private:
    char _attribute3;
    // int _attribute4;
};
void serialize(const A &) {
    static_assert(sizeof(A) == sizeof(Check<Bool, Bool, Char>), "!");
    // do whatever you want here...
}
int main() {
    serialize(A{});
}

基本思想是列出数据成员的所有类型,并使用混合蛋白从中定义新类型。然后是将static_assert放在正确的位置的问题。
请注意,私人数据成员也被考虑。

存在某些角色可能会破坏它,但也许它可以适用于您的真实代码。


作为旁注,如果是C 14,则可以进一步简化:

#include<cstddef>
template<typename... T>
constexpr std::size_t size() {
    std::size_t s = 0;
    std::size_t _[] = { s += sizeof(T)... };
    (void)_;
    return s;
}
class A final {
    bool _attribute1;
    bool _attribute2;
private:
    char _attribute3;
    // int _attribute4;
};
void serialize(const A &) {
    static_assert(sizeof(A) == size<bool, bool, char>(), "!");
    // ...
}
int main() {
    serialize(A{});
}

如果您注定要使用C 11,并且您仍然有兴趣仅使用列表初始化使用给定参数构建类型类型,但甚至没有任何类型的类型:

#include <type_traits>
struct default_param {
    template <class T>
    operator T();
};
template <class T, class...>
using typer = T;
template <class, class, class... Args>
struct cannot_one_more: std::true_type {};
template <class Tested, class... Args>
struct cannot_one_more<typer<void, decltype(Tested{std::declval<Args>()..., default_param{}})>, Tested, Args...>: std::false_type {
};
template <class...>
struct is_list_constructable: std::false_type {};
template <class Tested, class... Args>
struct is_list_constructable<Tested(Args...)>: is_list_constructable<void, Tested, Args...> { };
template <class Tested, class... Args>
struct is_list_constructable<typer<void, decltype(Tested{std::declval<Args>()...}), typename std::enable_if<cannot_one_more<void, Tested, Args...>::value>::type>, Tested, Args...>: std::true_type { };
struct S {
    bool b;
    //bool c; // causes error
};
int main() {
    static_assert(is_list_constructable<S(bool)>::value, "!");
}

[live demo]