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

C++ 幻影类型是否与原始类型具有相同的对齐方式?,c++,struct,types,language-lawyer,memory-alignment,C++,Struct,Types,Language Lawyer,Memory Alignment,考虑以下包含一些环境值的结构: struct environment_values { uint16_t humidity; uint16_t temperature; uint16_t charging; }; 我想向具有幻影类型*的值添加一些附加信息,同时使它们的类型不同: template <typename T, typename P> struct Tagged { T value; }; // Actual implementation will

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

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;
};
对于我迄今为止尝试过的所有类型,以下断言都适用:

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>() …
模板
constexpr无效检查(){
静态断言(alignof(T)=alignof(taged),“对齐不同”);
静态断言(sizeof(T)=sizeof(taged),“大小不同”);
}
//检查(),检查(),检查()…
既然标记和未标记变体的大小也是相同的,我想答案应该是肯定的,但我想有一些确定性

我不知道C++中如何调用这些标记值。“强类型typedefs”?我取了Haskell的名字。

标准中提到:

对象类型具有对齐要求(3.9.1、3.9.2),其中 对该类型对象的地址的限制 分配的对齐是实现定义的整数值 表示连续地址之间的字节数,其中 可以分配给定的对象。对象类型强制使用对齐 对该类型每件物品的要求;可能需要更严格的校准 使用对齐说明符(7.6.2)请求

此外,还提到:

指针类型的值表示由实现定义。 指向布局兼容类型的指针应具有相同的值 表示和对齐要求(6.11)。[注:指向 过对齐类型(6.11)没有特殊表示,但其 有效值的范围受扩展对齐限制 要求]

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


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

如前所述,为了使两个元素具有布局兼容性,它们都必须是标准布局类型,并且它们的非静态数据成员必须以相同的类型和顺序出现


struct{T m;}
只包含一个
T
,但
T
是一个
T
,因此它不能包含一个
T
作为其第一个非静态数据成员。

根据法律规定,类型的大小和对齐是由实现定义的,该标准对
sizeof
alignof
将返回的内容几乎没有任何保证

template <typename T, typename P>
struct Tagged {
    T value;
};
模板
结构标记{
T值;
};
理论上,编译器可以在这个结构的末尾添加填充,这显然会改变大小,可能还会改变对齐方式。在实践中,我唯一能想象这种情况发生的时候是
T
被赋予某种特定于编译器的“打包”属性,但
标记的
没有(但即使如此,)


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

正如gsamaras所提到的,该标准保证了布局兼容类的相同对齐


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

在12.2.21中,本标准规定了布局兼容等级的要求:

如果两个标准布局结构(第12条)类型的通用初始序列包含两个类(6.9)的所有成员和位字段,则它们是布局兼容类

通用初始序列的定义见12.2.20:

两种标准布局结构(第12条)类型的公共初始序列是按声明顺序排列的非静态数据成员和位字段的最长序列,从每个结构中的第一个这样的实体开始,这样,相应的实体具有布局兼容类型,并且两个实体都不是位字段,或者都是具有相同宽度的位字段。[示例:
结构A{int A;char b;};

结构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\u T
    ,信不信由你,这取决于标准。)*
  • 即使保证
    T
    是标准布局类,
    struct{tm;}
    也没有与
    T
    相同的初始序列。
    struct{T m;}
    的序列以
    T
    开始,而
    T
    的序列以
    T
    的非静态数据成员开始。实际上,严格保证这不是
    T
    ,因为类不能通过值来包含自己
  • 因此,保函不能以本标准的文字形式持有。您应该继续执行
    static\u assert
    操作,以确保编译器以您期望的方式运行

    *请参阅有关联合类型双关的大多数问题。

    嗯,[basic.align]有一个对齐是一个实现
    template <typename T, typename P>
    struct Tagged {
        T value;
    };