以C++方式为结构和数组着色

Aliasing struct and array the C++ way

本文关键字:数组 结构 C++ 方式      更新时间:2023-10-16

这是我另一个问题的C++跟进

在ISO C之前的旧时代,以下代码不会让任何人感到惊讶:

struct Point {
double x;
double y;
double z;
};
double dist(struct Point *p1, struct Point *p2) {
double d2 = 0;
double *coord1 = &p1->x;
double *coord2 = &p2->x;
int i;
for (i=0; i<3; i++) {
double d = coord2[i]  - coord1[i];    // THE problem
d2 += d * d;
}
return sqrt(d2);
}

不幸的是,这个有问题的行使用指针算法(p[i]根据定义*(p + i))在任何数组之外,这是标准明确不允许的。C++17的草案4659在8.7中说:

如果表达式 P 指向具有 n 个元素的数组对象 x 的元素 x[i], 表达式 P + J 和 J + P(其中 J 的值为 j)指向(可能假设的)元素 x[i + j] 如果 0 <= i + j <= n;否则,行为是未定义的。

(非规范性)注释 86 使其更加明确:

为此目的,不是数组元素

的对象被视为属于单元素数组。一个 经过 n 个元素的数组 x 的最后一个元素的指针被视为等效于指向假设元素的指针 x[n] 用于此目的。

引用问题的公认答案使用了 C 语言通过联合接受类型双关语的事实,但我永远无法在 C++ 标准中找到等价物。所以我假设一个包含匿名结构成员和一个数组的联合会导致C++Undefined Behaviour——它们是不同的语言......

问题:

什么是循环访问结构成员的一致性方法,就好像它们是 C++ 中数组的成员一样?我正在当前(C++17)版本中寻找一种方法,但也欢迎旧版本的解决方案。

免責聲明:

它显然仅适用于相同类型的元素,并且可以通过一个简单的assert来检测填充,如另一个问题所示,因此填充、对齐和混合类型不是我在这里的问题。

使用指向成员的指针的 constexpr 数组:

#include <math.h>
struct Point {
double x;
double y;
double z;
};
double dist(struct Point *p1, struct Point *p2) {
constexpr double Point::* coords[3] = {&Point::x, &Point::y, &Point::z};
double d2 = 0;
for (int i=0; i<3; i++) {
double d = p1->*coords[i] - p2->*coords[i];
d2 += d * d;
}
return sqrt(d2);
}

恕我直言,最简单的方法是实现operator[]。您可以像这样创建一个帮助程序数组,或者只是创建一个开关...

struct Point
{
double const& operator[] (std::size_t i) const 
{
const std::array coords {&x, &y, &z};
return *coords[i];
}
double& operator[] (std::size_t i) 
{
const std::array coords {&x, &y, &z};
return *coords[i];
}
double x;
double y;
double z;
};
int main() 
{
Point p {1, 2, 3};
std::cout << p[2] - p[1];
return 0;
}
struct Point {
double x;
double y;
double z;
double& operator[]( std::size_t i ) {
auto self = reinterpret_cast<uintptr_t>( this );
auto v = self+i*sizeof(double);
return *reinterpret_cast<double*>(v);
}
double const& operator[]( std::size_t i ) const {
auto self = reinterpret_cast<uintptr_t>( this );
auto v = self+i*sizeof(double);
return *reinterpret_cast<double const*>(v);
}
};

这取决于您的"结构"中的double之间没有包装。 断言这一点很困难。

POD 结构是保证的字节序列。

编译器应该能够将[]编译为与原始数组访问或指针算术相同的指令(或缺少指令)。可能存在一些问题,即此优化发生"太晚",无法进行其他优化,因此请仔细检查性能敏感代码。

转换为char*std::byte*而不是uintptr_t可能是有效的,但在这种情况下是否允许指针算术存在一个核心问题。

您可以使用以下事实:将指针强制转换为intptr_t执行算术运算,然后将值转换回指针类型是实现定义的行为。我相信它将适用于大多数编译器:

template<class T>
T* increment_pointer(T* a){
return reinterpret_cast<T*>(reinterpret_cast<intptr_t>(a)+sizeof(T));
}

这种技术是最有效的,如果使用表查找,优化器似乎无法产生最佳效果:组件比较