我有一个数组包装器,用作矩阵/向量类,还有两个浮点数的结构来表示点。
当我已经可以将它们用于矢量时,我不想再次为点重新定义所有算术运算符,因此我想在它们之间添加隐式转换。我使用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(); }
};
使用从Point
到TColVector<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
答案 0 :(得分:3)
您的代码具有未定义的行为。您不能仅将copy
的{{1}}改成reinterpret_cast
。仅此转换的结果可能是不确定的(此处的Point*
调用[expr.reinterpret.cast]/7到Array<Array<float, 2>, 1>*
[expr.static.cast]/4 [conv.ptr]/2的指针转换(仍然可以),然后进行转换从reinterpret_cast
到void*
的范围,如果void*
的对齐方式的严格程度不如Array<Array<float, 2>, 1>*
[expr.static.cast]/13的对齐方式,则可能无法指定。即使强制转换本身可以解决,也不允许您取消引用结果指针并访问结果左值所引用的对象。这样做会违反严格的别名规则[basic.lval]/11(有关更多信息,请参见here和here)。实际上,您的两种类型最终可能具有相同的内存布局,但是它们不是指针可互换的[basic.compound]/4。打印中间结果很可能使编译器无法根据那里的未定义行为执行优化,这就是为什么问题没有那么明显的原因。
恐怕您还必须考虑其他解决方案,例如,仅为Point
实现必要的运算符。或者只返回从Array<Array<float, 2>, 1>
和Point
初始化的Array<Array<float, 2>, 1>
。如果仅在表达式中使用,x
通常最终无论如何都会被优化(由于相关部分都是此处的模板,因此它们的定义是已知的,编译器应内联所有这些内容)。或者将您的y
成为列向量
Array<Array<float, 2>, 1>
并定义一些访问器函数以获取Point
和struct Point : TColVector<float, 2> {};
…