Ошибка при использовании reinterpret_cast между двумя похожими классами? - PullRequest
0 голосов
/ 12 мая 2019

У меня есть обертка массива, которую я использую в качестве класса матрицы / вектора, и структура из двух чисел с плавающей точкой для представления точек.
Я не хочу снова определять все арифметические операторы для точек, когда они уже доступны для векторов, поэтому я хочу добавить неявные преобразования между ними. Я использую 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> я получаю неверные результаты. Даже более странно: результаты верны, пока я печатаю промежуточные результаты, но неверны, когда я закомментирую операторы печати. И, кажется, это всегда правильно на gcc 7.3.0 для x86, а иногда и на gcc 8.3.0 для ARMv7.

Это функция, которая дала правильный результат с инструкциями печати и неправильный результат, когда я закомментировал операторы печати:

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;
}

Я что-то здесь не так делаю?

Решение, по-видимому, таково (даже если оно не работает как lvalue):

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

Я пытался изолировать проблему от остальной части моего проекта, но я не смог воспроизвести ее изолированно, поэтому я хотел бы знать, нужно ли мне продолжать поиск других проблем, даже если кажется, что хорошо пока.

Вот весь код на GitHub (похоже, проблема не показана отдельно): https://github.com/tttapa/random/blob/master/SO-reinterpret_cast.cpp

1 Ответ

3 голосов
/ 12 мая 2019

Ваш код имеет неопределенное поведение.Вы не можете просто reinterpret_cast a Point* до Array<Array<float, 2>, 1>*.Результат только этого приведения потенциально не определен (reinterpret_cast здесь вызывает [expr.reinterpret.cast] / 7 преобразование указателя в void* [expr.static.cast] / 4 [conv.ptr] / 2 (все еще в порядке) с последующим преобразованием из void* в Array<Array<float, 2>, 1>*, которое может быть неопределенным, если выравнивание Point окажется не нанаименее строгий, чем у Array<Array<float, 2>, 1> [expr.static.cast] / 13 ).Даже если само приведение произойдет, вы не сможете разыменовать результирующий указатель и получить доступ к объекту, на который ссылается результирующее lvalue.Это нарушит строгое правило псевдонимов [basic.lval] / 11 (см., Например, здесь и здесь для получения дополнительной информации).Ваши два типа могут в конечном итоге иметь одинаковую структуру памяти на практике, но они не являются взаимозаменяемыми по указателю [basic.compound] / 4 .Печать промежуточных результатов, скорее всего, не позволяет компилятору выполнять оптимизацию на основе неопределенного поведения, поэтому проблема не проявляется тогда ...

Вам придется подумать о каком-то другом решении, которое я боюсь,например, просто реализуя необходимые операторы для Point.Или просто верните Array<Array<float, 2>, 1>, инициализированный из ваших x и y.Если использовать только в выражениях, то Array<Array<float, 2>, 1> обычно в любом случае должно быть в конечном итоге оптимизировано (поскольку все соответствующие части являются здесь шаблонами, их определение будет известно, и компилятор должен встроить весь этот материал).Или сделайте ваш Point be вектором столбца

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

и определите некоторые функции доступа для получения x(point) и y(point)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...