C++ 在C+;中向类添加成员时导致编译时错误的技术+;

C++ 在C+;中向类添加成员时导致编译时错误的技术+;,c++,reflection,C++,Reflection,假设您有一些包含多个公共成员的POD。您还可以使用一些类从各种源序列化和反序列化这些对象。除了单元测试之外,是否有其他编码技术可以确保在向类中添加成员时,可以更新需要了解所添加字段的任何其他代码 即,我喜欢一种技术,它会导致编译时错误,直到所有代码被更新来处理新字段。 < P> C++中没有反射。 但是一个解决方案,也有一个吸引人的特性,即它不会污染编译后的代码,就是使用一个静态断言(sizeof(YourType)==x,),其中x是一个硬编码常量,取决于编译器 添加到YourType的任何成

假设您有一些包含多个公共成员的POD。您还可以使用一些类从各种源序列化和反序列化这些对象。除了单元测试之外,是否有其他编码技术可以确保在向类中添加成员时,可以更新需要了解所添加字段的任何其他代码


即,我喜欢一种技术,它会导致编译时错误,直到所有代码被更新来处理新字段。

< P> C++中没有反射。 但是一个解决方案,也有一个吸引人的特性,即它不会污染编译后的代码,就是使用一个
静态断言(sizeof(YourType)==x,)
,其中
x
是一个硬编码常量,取决于编译器

添加到
YourType
的任何成员都将更改
sizeof
,并导致编译时失败


(在某些情况下,如果一个新成员占用了以前结构填充结束时的空间,则此方法可能不起作用。请首先与编译器一起尝试此方法,看看是否可行。)

c++17开始,您可以利用结构化绑定:向代码库添加一个约定,即每当函数要求POD为最新版本时,该函数都应通过结构化绑定访问POD:

struct A { int a,b,c; };

void foo( A& the_pod )
{
  auto& [a,b,c] = the_pod;

  // ... use a,b,c
}
将成员添加到将使代码不再编译

请注意,在更改编译器时,依赖A的sizeof可能会破坏代码(如果依赖A的函数(如序列化代码)恰好是可移植的,这是不好的)

此外,这个错误会告诉你哪里出了问题,而这个解决方案也会告诉你哪里出了问题


我看到的唯一缺点是它依赖于程序员约定(但是,假设一个只会增长的POD也是一个约定…

在c++11中,如果默认聚合类型是可默认构造的,则可以完全反映默认对齐的聚合类型(注[1]);仅构造反射id非DC。这将是棘手的,但素描不是那么难(信贷:魔术)

  • 请注意,这些类型有一个包含类的所有类型的ctor,并且没有其他ctor接受更多参数。因此,你可以写:


  • 这与序列化密切相关。我想我可以在Yakk那里找到一个非常好的答案,它讨论了一种将数据成员导出为元组的技术。但是,在我们真正得到反射之前,如果没有一点不便,可能是不可能的。您可以创建一个成员的中心列表,并让代码的其余部分使用该列表。或者,您可以使用自动生成该列表。如果您使用一些库作为boost hana和
    boost\u hana\u DEFINE\u STRUCT
    ,您可以使用反射来编写代码。所谓POC,您是指POD吗?顺便说一句,为什么不为您的POD添加一些编译时版本控制逻辑?我相信我们可以想出其他人为的方法来打破这一局面。就像给工会增加一个新成员。或者更改指针的类型。@AndyG:我只是想解决“向类中添加成员”的问题。不错。当然是一种合理的保护。如果POC=POD(甚至聚合),那么就存在反射,因为c++11另一个不利的一面是,如果在结构中切换a、b或c的顺序,那么最终会绑定到错误的成员。@b是的,这是“约定”的一部分。。。事实上,如果我是你,我会提出一些编译时pod版本控制逻辑,而不是重温这里出现的任何技巧:)…哇,我错过了,太棒了!(我没有因为你没有(或至少指出)一些适用于OP问题的真实代码而投票;你能添加一个到Antony库的链接吗?)但有两点需要注意:1。Antony的库是c++14(如上所述,易于进行后端口);2.您将希望自己滚动,因为他的版本需要(最少数量的)每个类型的显式描述,这些类型可能是一个字段。Oto,他的代码是std,甚至用于读取,我的代码是std,用于构造/验证,用于读取的安腾ABI。
    template<typename T, typename... Args>
    using constructed_from = decltype(T{std::forward<Args>(std::declval<Args>())...});
    
    template<size_t i>
    struct Ubiq {
        template<typename T>
        operator T() const; /* undef'd */
    };
    
    #include <string>
    #include <type_traits>
    
    struct S1 {
        int i;
        std::string j;
    };
    
    struct S2 {
        int i;
        std::string j;
        int k;
    };
    
    template<typename T, typename... Args>
    using constructed_from =
        typename std::decay<decltype(
                    T{ std::forward<Args>(std::declval<Args>())... }
                 )>::type;
    
    template<size_t i>
    struct Ubiq {
        template<typename T>
        operator T() const; /* undef'd */
    };
    
    template<typename... Ts> struct make_void { typedef void type;};
    template<typename... Ts> using void_t = typename make_void<Ts...>::type;
    
    template<typename T, typename AlwaysVoid, typename... Args>
    struct is_constructible_from : std::false_type {};
    
    template<typename T, typename... Args>
    struct is_constructible_from<T, void_t<constructed_from<T, Args...>>, Args...> : std::true_type {};
    
    static_assert( is_constructible_from<S1, void, int, std::string>::value, "");
    static_assert(!is_constructible_from<S1, void, int, std::string, Ubiq<0>>::value, ""); // note: !
    
    static_assert( is_constructible_from<S2, void, int, std::string>::value, "");
    static_assert( is_constructible_from<S2, void, int, std::string, Ubiq<0>>::value, ""); // note: no !