I have an array wrapper that I use as a matrix/vector class, and a struct of two floats to represent points.
I don't want to re-define all arithmetic operators again for points, when I already have them available for vectors, so I want to add implicit conversions between them. I use reinterpret_cast
, as shown in the snippet below.
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(); }
};
When using the implicit conversion from Point
to TColVector<float, 2>
, I get incorrect results. Even stranger: the results are correct as long as I print the intermediate results, but incorrect when I comment out the print statements. And it seems to be always correct on gcc 7.3.0 for x86, and sometimes incorrect on gcc 8.3.0 for ARMv7.
This is the function that gave a correct result with the print statements, and an incorrect result when I commented out the print statements:
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;
}
Am I doing something wrong here?
The solution seems to be this (even though it doesn't work as an lvalue):
TColVector<float, 2> vec() const { return {x, y}; }
I tried to isolate the problem from the rest of my project, but I haven't been able to reproduce it in isolation, so I would like to know if I have to keep on looking for other problems, even though it seems alright for now.
Here's the entire code on GitHub (it doesn't seem to demonstrate the problem in isolation): https://github.com/tttapa/random/blob/master/SO-reinterpret_cast.cpp
Your code has undefined behavior. You can't just reinterpret_cast
a Point*
to an Array<Array<float, 2>, 1>*
. The result of this cast alone is potentially unspecified (the reinterpret_cast
here invokes [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., here 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. Printing the intermediate results most likely keeps the compiler from performing optimizations based on the undefined behavior there, that's why the problem doesn't manifest then…
You'll have to think of some other solution I'm afraid, e.g, just implementing the necessary operators for Point
. Or just return an Array<Array<float, 2>, 1>
initialized from your x
and y
. If only used in expressions, that Array<Array<float, 2>, 1>
should typically end up being optimized away anyways (since the relevant parts are all templates here, their definition will be known and the compiler should inline all this stuff). Or make your Point
be a column vector
struct Point : TColVector<float, 2> {};
and define some accessor functions to get x(point)
and y(point)
…