幻像类型是否与原始类型具有相同的对齐方式?

Does a phantom type have the same alignment as the original one?

考虑以下包含一些环境值的结构:

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++ 中如何调用这些标记值。 "Strongly typed typedefs"?我取自 Haskell.

的名字

标准在[basic.align]/1中提到:

Object types have alignment requirements (3.9.1, 3.9.2) which place restrictions on the addresses at which an object of that type may be allocated. An alignment is an implementation-defined integer value representing the number of bytes between successive addresses at which a given object can be allocated. An object type imposes an alignment requirement on every object of that type; stricter alignment can be requested using the alignment specifier (7.6.2).

此外,[basic.compound]/3,提到:

The value representation of pointer types is implementation-defined. Pointers to layout-compatible types shall have the same value representation and alignment requirements (6.11). [Note: Pointers to over-aligned types (6.11) have no special representation, but their range of valid values is restricted by the extended alignment requirement].

因此,可以保证布局兼容的类型具有相同的对齐方式。

struct { T m; }T 布局不兼容。

正如所指出的 ,为了使两个元素与布局兼容,它们都必须是标准布局类型,并且它们的非静态数据成员必须以相同的类型出现在相同的位置订单。

struct { T m; } 只包含一个 T,但是 T 是一个 T,所以它不能包含一个 T 作为它的第一个非静态数据成员.

根据法律条文,类型的大小和对齐方式是实现定义的,标准几乎无法保证 sizeofalignof 将 return .

template <typename T, typename P>
struct Tagged {
    T value;
};

理论上,允许编译器在此结构的末尾添加填充,这显然会改变大小,也可能会改变对齐方式。实际上,我唯一能想到这种情况发生的情况是 T 被赋予了某种特定于编译器的 "packed" 属性,但 Tagged 却没有(但即便如此,GCC seems to work okay).

无论如何,我认为添加一些静态断言以确保编译器是合理的是个好主意——这正是您所做的:)。

正如 gsamaras 所提到的,该标准保证 layout compatible classes.

具有相同的对齐方式

遗憾的是,struct { T m; }T 布局不兼容

在 12.2.21 中,标准规定了 布局兼容 class:

的要求

Two standard-layout struct (Clause 12) types are layout-compatible classes if their common initial sequence comprises all members and bit-fields of both classes (6.9).

公共初始序列的定义在12.2.20:

The common initial sequence of two standard-layout struct (Clause 12) types is the longest sequence of non-static data members and bit-fields in declaration order, starting with the first such entity in each of the structs, such that corresponding entities have layout-compatible types and either neither entity is a bit-field or both are bit-fields with the same width. [Example:
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; };
The common initial sequence of A and B comprises all members of either class. The common initial sequence of A and C and of A and D comprises the first member in each case. The common initial sequence of A and E is empty.
— end example]

由此我们可以得出以下重要观察结果:

  1. 布局兼容性严格限于标准布局 classes。 (或者当 TT2 实际上是完全相同的类型时,枚举使用相同的底层类型或简单的情况。参见 6.9.11。)在一般情况下,T 不是标准布局 class。实际上,T 在您的示例中甚至不是 class(它是 uint16_t,信不信由你,根据标准这很重要。)*
  2. 即使 T 保证是标准布局 class,struct { T m; } 也没有与 T 相同的初始序列。 struct { T m; } 的序列以 T 开始,而 T 的序列以任何 T 的非静态数据成员开始。这实际上严格保证不是 T 因为 class 不能按值包含自己。

因此,不能以标准的字母来保证。您应该继续执行 static_assertions 以确保您的编译器以您期望的方式运行。

* 查看关于联合类型双关语的大多数问题。