在两个类似的类之间使用reinterpret_cast时会出错吗?

时间:2019-05-11 22:04:45

标签: c++ undefined-behavior reinterpret-cast

我有一个数组包装器,用作矩阵/向量类,还有两个浮点数的结构来表示点。
当我已经可以将它们用于矢量时,我不想再次为点重新定义所有算术运算符,因此我想在它们之间添加隐式转换。我使用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

1 个答案:

答案 0 :(得分:3)

您的代码具有未定义的行为。您不能仅将copy的{​​{1}}改成reinterpret_cast。仅此转换的结果可能是不确定的(此处的Point*调用[expr.reinterpret.cast]/7Array<Array<float, 2>, 1>* [expr.static.cast]/4 [conv.ptr]/2的指针转换(仍然可以),然后进行转换从reinterpret_castvoid*的范围,如果void*的对齐方式的严格程度不如Array<Array<float, 2>, 1>* [expr.static.cast]/13的对齐方式,则可能无法指定。即使强制转换本身可以解决,也不允许您取消引用结果指针并访问结果左值所引用的对象。这样做会违反严格的别名规则[basic.lval]/11(有关更多信息,请参见herehere)。实际上,您的两种类型最终可能具有相同的内存布局,但是它们不是指针可互换的[basic.compound]/4。打印中间结果很可能使编译器无法根据那里的未定义行为执行优化,这就是为什么问题没有那么明显的原因。

恐怕您还必须考虑其他解决方案,例如,仅为Point实现必要的运算符。或者只返回从Array<Array<float, 2>, 1>Point初始化的Array<Array<float, 2>, 1>。如果仅在表达式中使用,x通常最终无论如何都会被优化(由于相关部分都是此处的模板,因此它们的定义是已知的,编译器应内联所有这些内容)。或者将您的y 成为列向量

Array<Array<float, 2>, 1>

并定义一些访问器函数以获取Pointstruct Point : TColVector<float, 2> {};