【问题标题】:`std::complex<T>[n]` and `T[n*2]` type aliasing`std::complex<T>[n]` 和 `T[n*2]` 类型别名
【发布时间】:2017-02-23 15:38:37
【问题描述】:

自 C++11 起,std::complex&lt;T&gt;[n] 保证可别名为 T[n*2],并具有明确定义的值。这正是任何主流架构所期望的。对于我自己的类型(例如struct vec3 { float x, y, z; }),这种保证是否可以通过标准 C++ 实现,还是只有在编译器的特殊支持下才能实现?

【问题讨论】:

  • 我们有一个特殊的语言例外来为标准库类型键入别名?每天学习新东西。

标签: c++ language-lawyer strict-aliasing type-punning


【解决方案1】:

TL;DR:编译器必须检查reinterpret_casts 并确定涉及std::complex 的(标准库)特化。我们不能一致地模仿语义。

我认为很明显将三个不同的成员视为数组元素是行不通的,因为指向它们的指针的指针运算受到极大限制(例如,加 1 会产生 pointer past-the-end)。

所以我们假设 vec3 包含一个由三个 ints 组成的数组。 即使这样,您隐含需要的底层reinterpret_cast&lt;int*&gt;(&amp;v)(其中vvec3)也不会给您留下指向第一个元素的指针。请参阅pointer-interconvertibility 上的详尽要求:

两个对象ab指针互转换 如果:

  • 它们是同一个对象,或者

  • 一个是标准布局联合对象,另一个是该对象的非静态数据成员 ([class.union]),或者

  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有 非静态数据成员,该对象的第一个基类子对象 ([class.mem]),或

  • 存在一个对象c,这样ac 是指针可互转换的,而cb 是 指针互转换。

如果两个对象是指针可互转换的,那么它们具有相同的 地址,并且可以从指针中获得指向一个的指针 通过reinterpret_­cast 发送给另一个。 [ 注意一个数组对象 并且它的第一个元素不是指针可互转换的,即使 他们有相同的地址。  — 尾注 ]

这是非常明确的;虽然我们可以获得指向数组的指针(作为第一个成员),并且虽然指针互转换是可传递的,但我们无法获得指向其第一个元素的指针。

最后,即使你设法获得了指向成员数组第一个元素的指针,如果你有一个 vec3s 数组,你也不能使用简单的方法遍历所有成员数组指针递增,因为我们得到的指针超过了它们之间的数组的末尾。 launder 也不能解决这个问题,因为与指针关联的对象不共享任何存储空间(具体参见 [ptr.launder])。

【讨论】:

  • launder 有什么帮助?另外,为什么数组不能与其第一个元素相互转换? (这些对于 cmets 来说可能太宽泛了......)
  • @Barry 后者可能是受到优化的启发。关于前者,您是对的,我忘记检查结构是否已打包。
  • 我认为您的替代解决方案中的第二个 launder 存在问题。第一个launder 确实会给你一个指向vecarr[0].a 的指针,但是int 对象不是数组元素,所以p + 1 没有指向 vecarr[0].b,它是指针超过vecarr[0].a 的结尾。因此,通过p + 1 ([ptr.launder]/3) 没有任何字节存储可达
  • 但是vecarr[0].b的存储中的字节可以通过laundered的值来访问,所以不能满足[ptr.launder]/1中的可达性要求。据我了解,您基本上不能launder 指针越过末尾并获得任何有用的东西。
  • @bogdan 我没有看launder 在 P0137r0 之后的措辞。你说的对。其实多维数组的平面迭代是完全不可能的,比较可惜!
【解决方案2】:

大部分情况下,只有在编译器的特殊支持下才有可能。

联合不能让您到达那里,因为通用方法实际上具有未定义的行为,尽管布局兼容的初始序列有例外,您可以通过unsigned char* 作为特殊情况检查对象。不过就是这样。

有趣的是,除非我们假设“低于”具有广泛而无用的含义,否则该标准在这方面在技术上是矛盾的:

[C++14: 5.2.10/1]: [..] 下面列出了可以使用 reinterpret_cast 显式执行的转换。无法使用 reinterpret_cast 显式执行其他转换。

没有提到complex&lt;T&gt; 的情况。最后,您所指的规则在[C++14: 26.4/4] 中介绍了很久很久。

【讨论】:

  • 不确定这是否矛盾。您引用的条款清楚地允许转换,但它还没有给出我们想要的含义(这就是 26.4 所做的)。
  • @Columbo:“清楚”吗? IRTA 转换 [可以说] 没有“在下面列出”,因此无法显式执行。就像他们在 26.4/4 中添加了新规则但忘记更新 5.2.10。
  • 什么转换?将一个对象左值转换为另一种对象类型的左值引用?这不是第 11 段吗?
【解决方案3】:

如果您的类型包含float x[3],并且您确保sizeof(vec3) == 3*sizeof(float) &amp;&amp; is_standard_layout_v&lt;vec3&gt;,我认为它适用于单个vec3。鉴于这些条件,标准保证第一个成员的偏移量为零,因此第一个 float 的地址是对象的地址,您可以执行数组算术以获取数组中的其他元素:

struct vec3 { float x[3]; } v = { };
float* x = reinterpret_cast<float*>(&v);  // points to first float
assert(x == v.x);
assert(&x[0] == &v.x[0]);
assert(&x[1] == &v.x[1]);
assert(&x[2] == &v.x[2]);

您不能将vec3 的数组视为长度的三倍的浮点数组。每个vec3 内的数组的数组运算将不允许您访问下一个vec3 内的数组。 CWG 2182 与此处相关。

【讨论】:

  • 这反而打败了对象,不是吗?
  • 我认为这不能保证有效;看我的回答。
猜你喜欢
  • 1970-01-01
  • 2011-08-03
  • 2017-10-03
  • 1970-01-01
  • 2019-03-23
  • 1970-01-01
  • 2012-06-08
  • 2015-05-08
  • 2021-02-27
相关资源
最近更新 更多