在两个相似的 类 之间使用 reinterpret_cast 时出现错误?

Bug when using reinterpret_cast between two similar classes?

我有一个用作 matrix/vector class 的数组包装器,以及一个由两个浮点数组成的结构来表示点。
我不想再次为点重新定义所有算术运算符,当我已经有了它们可用于向量时,所以我想在它们之间添加隐式转换。我使用 reinterpret_cast,如下面的代码片段所示。

template <class T, size_t N>
struct Array {
    T data[N];
    constexpr T &operator[](size_t index) { return data[index]; }
    constexpr const T &operator[](size_t index) const { return data[index]; }
};

template <class T, size_t R, size_t C>
using TMatrix = Array<Array<T, C>, R>;

template <class T, size_t R>
using TColVector = TMatrix<T, R, 1>;

struct Point {
    float x;
    float y;

    constexpr Point(float x, float y) : x{x}, y{y} {}
    constexpr Point(const TColVector<float, 2> &vec) : x{vec[0]}, y{vec[1]} {}

    TColVector<float, 2> &vec() {
        static_assert(sizeof(*this) == sizeof(TColVector<float, 2>));
        return *reinterpret_cast<TColVector<float, 2> *>(this);
    }
    operator TColVector<float, 2> &() { return vec(); }
};

当使用从 PointTColVector<float, 2> 的隐式转换时,我得到了不正确的结果。更奇怪的是:只要我打印中间结果,结果就是正确的,但是当我注释掉打印语句时,结果就不正确。它似乎在 x86 的 gcc 7.3.0 上总是正确的,有时在 ARMv7 的 gcc 8.3.0 上不正确。

这个函数在打印语句时给出了正确的结果,而在我注释掉打印语句时给出了错误的结果:

static float distanceSquared(Point a, Point b) {
    using namespace std;
    // cout << "a = " << a << ", b = " << b << endl;
    auto diff = a.vec() - b.vec(); // Array<T, N> operator-(const Array<T, N> &lhs, const Array<T, N> &rhs)
    // cout << "diff = " << Point(diff) << endl;
    auto result = normsq(diff); // auto normsq(const TColVector<T, C> &colvector) -> decltype(colvector[0] * colvector[0])
    // cout << "normsq(diff) = " << result << endl;
    return result;
}

我是不是做错了什么?

解决方案似乎是这样的(即使它不能用作左值):

TColVector<float, 2> vec() const { return {x, y}; }

我试图将问题与我的项目的其余部分隔离开来,但我无法单独重现它,所以我想知道我是否必须继续寻找其他问题,即使目前看来还可以。

这是 GitHub 上的完整代码(它似乎并没有单独说明问题):https://github.com/tttapa/random/blob/master/SO-reinterpret_cast.cpp

您的代码有未定义的行为。你不能只是 reinterpret_cast 一个 Point* 到一个 Array<Array<float, 2>, 1>*。仅此转换的结果可能未指定(此处的 reinterpret_cast 调用 [expr.reinterpret.cast]/7 a pointer conversion to void* [expr.static.cast]/4 [conv.ptr]/2 (still fine) followed by a conversion from void* to Array<Array<float, 2>, 1>*, which may be unspecified if the alignment of Point turns out not to be at least as strict as that of Array<Array<float, 2>, 1> [expr.static.cast]/13). Even if the cast itself happens to work out, you're not allowed to dereference the resulting pointer and access the object the resulting lvalue refers to. Doing so would violate the strict aliasing rule [basic.lval]/11 (see, e.g., and here for more). Your two types may end up having the same memory layout in practice, but they are not pointer-interconvertible [basic.compound]/4。打印中间结果很可能会阻止编译器根据那里的未定义行为执行优化,这就是问题不存在的原因然后显现...

恐怕您必须考虑其他一些解决方案,例如,只为 Point 实施必要的运算符。或者只是 return 一个 Array<Array<float, 2>, 1> 从你的 xy 初始化。如果只用在表达式中,那 Array<Array<float, 2>, 1> 通常应该最终被优化掉(因为相关部分在这里都是模板,它们的定义是已知的,编译器应该内联所有这些东西)。或者让你的 Point 成为 列向量

struct Point : TColVector<float, 2> {};

并定义一些访问函数来获取 x(point)y(point)