Приводит ли reinterpret_cast к неопределенному поведению?

У меня есть шаблон класса A, который содержит контейнер указателей (T*):

template <typename T>
class A {
public:
   // ... 
private:
   std::vector<T*> data;
};

и кучу функций вроде:

void f(const A<const T>&);
void g(const A<const T>&);

Можно ли вызывать эти функции через приведение от A<const T> к A<T>?

A<double> a;
... 
auto& ac = reinterpret_cast<const A<const double>&>(a);
f(ac);

Я почти уверен, что этот код имеет неопределенное поведение.

Опасно ли использовать такие преобразования в реальной жизни?


person Sergei    schedule 09.05.2016    source источник
comment
Если это UB, то в реальной жизни использовать их однозначно опасно. Однако это подозрительно похоже на проблему XY.   -  person erip    schedule 09.05.2016
comment
Полагаться на UB всегда опасно, даже если он работает в реальном мире. Подумайте о таких проектах, как Qt-5, Chromium, KDevelop, которые были сломаны gcc6, потому что использовались некоторые this == nullptr ерунды.   -  person Baum mit Augen    schedule 09.05.2016
comment
@Holt Может быть неизменяемый интерфейс.   -  person erip    schedule 09.05.2016
comment
Вам следует прочитать раздел 5.2.10 Reinterpret cast в стандарте: open -std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf   -  person Jesper Juhl    schedule 09.05.2016
comment
@erip Я обновил вопрос. Есть ли у вас предложения по решению проблемы?   -  person Sergei    schedule 10.05.2016
comment
@Holt Один и тот же метод может быть вызван как с A<const T>, так и с A<T>. Я не могу оставить только A<T> и выбросить A<const T>, иначе я потеряю const-корректность.   -  person Sergei    schedule 10.05.2016


Ответы (2)


Поскольку A<double> и A<const double> являются несвязанными типами, на самом деле это неуказанное (изначально я думал undefined) поведение и, соответственно, да, это плохая идея для использования в реальной жизни: вы никогда не знаете, какую систему (ы) или компилятор (ы) вы можете перенести на это изменение поведение странное.

Ссылка:

5.2.10/11:

Выражение lvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» может быть явно преобразовано в тип «указатель на T2» с помощью reinterpret_cast. То есть ссылочное приведение reinterpret_cast (x) имеет тот же эффект, что и преобразование * reinterpret_cast (& x) со встроенными операторами & и * (и аналогично для reinterpret_cast (x)).

Поэтому они перенаправили нас в более ранний раздел 5.2.10 / 7:

Указатель объекта может быть явно преобразован в указатель объекта другого типа. ... ... Преобразование prvalue типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 являются типами объектов и где требования выравнивания для T2 не строже, чем для T1) и обратно в его исходный тип дает исходное значение указателя. Результат любого другого такого преобразования указателя не указан.

Если f и g - это алгоритмы, которые работают с контейнерами, простое решение - заменить их шаблонными алгоритмами, которые работают с диапазонами (парами итераторов).

person Mark B    schedule 09.05.2016
comment
Я согласен, но в этом случае мне не удалось увидеть что-либо в стандартной версии UB. У вас есть ссылка? - person erip; 09.05.2016
comment
@erip Это нарушает строгий псевдоним, верно? Так что, даже если бы расположение этих двух было одинаковым, все равно это был бы UB. - person TartanLlama; 09.05.2016
comment
@MarkB Я все еще думаю, что это UB под [basic.lval]/10 (по крайней мере, если OP продолжит доступ к параметрам). - person TartanLlama; 09.05.2016
comment
@MarkB Я этого боялся. Я обновил вопрос. Не могли бы вы что-нибудь предложить по этому поводу? - person Sergei; 10.05.2016

Хотя сам reinterpret_cast может быть неопределенным поведением, попытка доступа к параметрам после того, как вы выполнили приведение, является неопределенным поведением.

N3337 [basic.lval]/10: Если программа пытается получить доступ к сохраненному значению объекта с помощью glvalue, отличного от одного из следующих типов, поведение не определено.

- динамический тип объекта,

- cv-квалифицированная версия динамического типа объекта,

- тип, аналогичный (как определено в 4.4) динамическому типу объекта,

- тип, который представляет собой знаковый или беззнаковый тип, соответствующий динамическому типу объекта,

- тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,

- тип агрегата или объединения, который включает один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический член данных субагрегата или содержащегося объединения),

- тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,

- тип char или unsigned char.

Ваш пример не соответствует ни одному из вышеперечисленных.

person TartanLlama    schedule 09.05.2016
comment
И вызов любой нестатической функции-члена на вещи нарушает [class.mfct.non-static] / 2, даже если он не обращается к членам класса. Практически везде UB, если полученная ссылка используется каким-либо образом. - person T.C.; 09.05.2016