考虑以下包含一些环境值的结构:
struct environment_values {
uint16_t humidity;
uint16_t temperature;
uint16_t charging;
};
我想为那些带有幻像类型*的值添加一些额外信息,并同时使它们的类型不同:
template <typename T, typename P>
struct Tagged {
T value;
};
// Actual implementation will contain some more features
struct Celsius{};
struct Power{};
struct Percent{};
struct Environment {
Tagged<uint16_t,Percent> humidity;
Tagged<uint16_t,Celsius> temperature;
Tagged<uint16_t,Power> charging;
};
Environment
的内存布局是否与environment_values
相同?这是否适用于混合类型布局,例如:
struct foo {
uint16_t value1;
uint8_t value2;
uint64_t value3;
}
struct Foo {
Tagged<uint16_t, Foo> Value1;
Tagged<uint8_t , Bar> Value2;
Tagged<uint64_t, Quux> Value3;
}
对于我迄今为止尝试过的所有类型,以下断言都是:
template <typename T, typename P = int>
constexpr void check() {
static_assert(alignof(T) == alignof(Tagged<T,P>), "alignment differs");
static_assert(sizeof(T) == sizeof(Tagged<T,P>), "size differs");
}
// check<uint16_t>(), check<uint32_t>(), check<char>() …
由于标记和未标记变体的大小也相同,我猜测答案应该是肯定的,但我希望有一定的确定性。
*我不知道在C ++中如何调用这些标记值。 “强类型typedef”?我从Haskell取名。
答案 0 :(得分:10)
标准在[basic.align]/1中提及:
对象类型具有对齐要求(3.9.1,3.9.2) 对该类型的对象可能的地址的限制 分配。 对齐是实现定义的整数值 表示连续地址之间的字节数 可以分配给定的对象。对象类型强制对齐 对该类型的每个对象的要求;更严格的对齐可以 请求使用对齐说明符(7.6.2)。
此外,[basic.compound]/3,提及:
指针类型的值表示是实现定义的。 指向布局兼容类型的指针应具有相同的值 表示和对齐要求(6.11)。 [注:指针指向 过度对齐的类型(6.11)没有特殊的表示,但他们的 有效值的范围受扩展对齐的限制 要求。
因此,可以保证布局兼容类型具有相同的对齐方式。
struct { T m; }
和T
不是布局兼容的。
如指向here,为了使两个元素兼容布局,它们都必须是标准布局类型,并且它们的非静态数据成员必须以相同的类型和相同的顺序出现。
struct { T m; }
仅包含T
,但T
是T
,因此它不能包含T
作为其第一个非静态数据成员。< / p>
答案 1 :(得分:7)
根据法律规定,类型的大小和排列是实施定义的,如果对sizeof
和alignof
将返回的内容有任何保证,标准会给你很少。
template <typename T, typename P>
struct Tagged {
T value;
};
理论上,允许编译器在此结构的末尾添加填充,这显然会改变大小,也可能改变对齐。在实践中,我唯一可以设想这种情况是T
是否给出了某种特定于编译器的“打包”属性,但Tagged
不是(但即便如此,GCC seems to work okay)
在任何情况下,我都会说添加一些静态断言来确保编译器是合理的 - 这正是你所做的:)。
答案 2 :(得分:3)
正如gsamaras所述,该标准保证布局兼容类的对齐方式相同。
不幸的是,struct { T m; }
和T
不符合布局。
在12.2.21中,该标准规定了布局兼容类的要求:
两个标准布局结构(第12节)类型是布局兼容类,如果它们的公共初始序列包含两个类的所有成员和位字段(6.9)
公共初始序列的定义见12.2.20:
两个标准布局结构(第12节)类型的公共初始序列是声明顺序中非静态数据成员和位字段的最长序列,从每个结构中的第一个这样的实体开始,使得对应的实体具有布局兼容的类型,并且实体都不是比特字段,或者两者都是具有相同宽度的比特字段。 [实施例:
struct A { int a; char b; };
struct B { const int b1; volatile char b2; };
struct C { int c; unsigned : 0; char b; };
struct D { int d; char b : 4; };
struct E { unsigned int e; char b; };
A
和B
的公共初始序列包含任一类的所有成员。A
和C
以及A
和D
的公共初始序列在每种情况下都包含第一个成员。A
和E
的公共初始序列为空 - 结束例子]
因此,我们可以做出以下重要观察:
T
和T2
字面上完全相同时,枚举使用相同的基础类型或简单的大小写。请参阅6.9.11。)在一般情况下,T
不是标准布局类。事实上,T
在您的示例中甚至不是一个类(它是uint16_t
,不管你信不信,这根据标准很重要。)* T
保证是标准布局类,struct { T m; }
也没有T
的公共初始序列。 struct { T m; }
的序列以T
开头,而T
的序列以T
的非静态数据成员开头。这实际上严格保证不是T
,因为类不能按值包含它自己。因此,该标准的字母不能保证。您应该继续执行static_assert
离子,以确保您的编译器以您期望的方式运行。
*查看关于工会类型惩罚的大多数问题。