你能通过 T 的结构来为 T 的数组起别名吗,类似于 std::complex<T>[N] 可以为 T[N * 2] 起别名吗?

问题描述 投票:0回答:3

自 C++11 起,

std::complex<T>[n]
保证可别名为
T[n*2]
,并具有明确定义的值。这正是人们对任何主流架构所期望的。

对于我自己的类型,这种保证可以通过标准 C++ 实现吗?

struct vec3 { float x, y, z; }
还是只有在编译器的特殊支持下才可能实现?

c++ language-lawyer strict-aliasing type-punning
3个回答
9
投票

TL;DR:编译器必须检查

reinterpret_cast
并找出涉及
std::complex
的(标准库)特化。我们无法一致地模仿语义。

我认为很明显,将三个不同的成员视为数组元素是行不通的,因为指向它们的指针的指针算术受到极大的限制(例如,加 1 会产生一个超过末尾的指针)。

因此,我们假设

vec3
包含三个
int
的数组。 即使这样,您隐式需要的底层
reinterpret_cast<int*>(&v)
(其中
v
vec3
)也不会为您留下指向第一个元素的指针。请参阅指针可互换性的详尽要求:

两个对象

a
b
指针可相互转换的,如果:

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

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

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

  • 存在一个对象

    c
    ,使得
    a
    c
    是指针可相互转换的,并且
    c
    b
    是 指针可相互转换。

如果两个对象是指针可相互转换的,那么它们具有相同的 地址,并且可以从指针获得指向一的指针 通过

reinterpret_­cast
到另一个。 [ 注意数组对象 和它的第一个元素不是指针可相互转换的,即使 他们有相同的地址。  — 尾注 ]

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

最后,即使你设法获得了指向成员数组第一个元素的指针,如果你有一个

vec3
数组,你也无法使用简单的指针增量来遍历所有成员数组,因为我们得到了指针超过中间数组的末尾。 launder 也不能解决这个问题,因为与指针关联的对象不共享任何存储(具体信息参见 [ptr.launder])。


5
投票

这只有在编译器的特殊支持下才有可能实现。

联合不会让你到达那里,因为常见的方法实际上具有未定义的行为,尽管布局兼容的初始序列有例外,并且你可以通过

unsigned char*
作为特殊情况检查对象。不过就是这样。

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

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

complex<T>
的情况就没有被提及。最后,您所指的规则是在很久以后的
[C++14: 26.4/4]
中引入的。


4
投票

我认为如果您的类型包含

vec3
,并且您确保
float x[3]
,那么它适用于单个
sizeof(vec3) == 3*sizeof(float) && is_standard_layout_v<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 与此处相关。

© www.soinside.com 2019 - 2024. All rights reserved.