Переосмыслить это в C ++: законно или нет? - PullRequest
0 голосов
/ 02 марта 2019

Это немного эзотерический вопрос, но мне было любопытно, является ли закономерным следующий шаблон расширения класса (как, например, UB) в современном C ++ (для всех намерений и целей я в порядке, ограничив обсуждение C ++17 и позже).

template<typename T>
struct AddOne {
    T add_one() const {
        T const& tref = *reinterpret_cast<T const*>(this);
        return tref + 1;
    }
};

template<template<typename> typename  E, typename T>
E<T> const& as(T const& obj) {
    return reinterpret_cast<E<T> const&>(obj);
} 

auto test(float x) {
    return as<AddOne>(x).add_one();
}

auto test1(int x) {
    return as<AddOne>(x).add_one();
}

// a main() to make this an MVCE
// will return with the exit code 16
int main(int argc, const char * argv[]) {
  return test1(15);
}

Приведенный выше код является полным примером, он компилирует, запускает и выдает ожидаемый результат, по крайней мере, clang в режиме C ++ 17.Проверьте код разборки в проводнике компилятора: https://godbolt.org/z/S3ZX2Y

Моя интерпретация следующая: стандарт гласит, что reinterpret_cast может конвертировать между указателями / ссылками любых типов, но при обращении к ним получающиеся ссылки могут быть UB (согласно правилам алиасинга).В то же время преобразование полученного значения обратно в исходный тип гарантирует получение исходного значения.

Исходя из этого, просто повторная ссылка на float как ссылка на AddOne<float> не вызывает UB.Поскольку мы никогда не пытаемся получить доступ к какой-либо памяти за этой ссылкой как к экземпляру AddOne<float>, здесь также нет UB.Мы используем только информацию о типе этой ссылки, чтобы выбрать правильную реализацию add_one() функции-члена.Сама функция скрывает ссылку на исходный тип, поэтому, опять же, нет UB.По сути, этот шаблон семантически эквивалентен этому:

template<typename T>
struct AddOne {
   static T add_one(T const& x) {
      return x + 1;
   }
};

auto test(float x) {
  return AddOne<Int>::add_one(x);
}

Я прав или есть что-то, что я здесь упускаю?

Считайте это академическим упражнением при изучении стандарта C ++.

Редактировать: это не дубликат Когда использовать reinterpret_cast? , поскольку в этом вопросе не обсуждается приведение указателя this или использование reinterpret_cast для отправки по переинтерпретированному типу.

Ответы [ 2 ]

0 голосов
/ 05 марта 2019

Полный ответ был предоставлен пользователем @nm в комментариях, поэтому я буду копировать его здесь для полноты картины.

Абзац из стандартных разделов [basic.life] c ++ гласит:

До начала жизни объекта [...] Программа имеет неопределенное поведение, если [...] указатель используется для доступа к нестатическому члену данных или вызова нестатической функции-члена объекта

Как представляется, это запрещает диспетчеризацию через переосмысленную ссылку, поскольку эта ссылка необратитесь к живому объекту.

0 голосов
/ 02 марта 2019

Нет, это определенно не законно.По ряду причин.

Первая причина в том, что у вас есть *this разыменование AddOne<int>*, которое на самом деле не указывает на AddOne<int>.Неважно, что операция не требует разыменования «за кадром»;*foo допустимо только в том случае, если foo указывает на объект совместимого типа.

Вторая причина аналогична: вы вызываете функцию-член для AddOne<int>, который не является.Также не имеет значения, что вы не обращаетесь ни к одному из AddOne (несуществующих) членов: сам вызов функции является доступом к значению объекта, противоречащему правилу строгого алиасинга.

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