#include <iostream>
int main(){
int& rf = reinterpret_cast<int&>((int&&)1);
}
В этом примере GCC
и MSVC
не принимают этот код. Вместо этого Clang
успешно компилирует его. Результат здесь.
Изучив соответствующее правило, соответствующее правило:
expr.reinterpret.cast#11
Значение gl типа T1, обозначающее объект x, может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» можно явно преобразовать в тип «указатель на T2» с помощью reinterpret_cast. Результатом является
*reinterpret_cast<T2 *>(p)
, где p — указатель на x типа «указатель на T1». Временные объекты не создаются, копии не создаются, конструкторы ([class.ctor]) или функции преобразования ([class.conv]) не вызываются.
Посмотрите на это правило, я думаю, что это правило очень расплывчато. Там написано, что результат такой же, как *reinterpret_cast<T2 *>(p)
. Правило разыменования указателя определяется как:
экспр.унарный#оп-1
Унарный оператор * выполняет косвенность: выражение, к которому он применяется, должно быть указателем на тип объекта или указателем на тип функции, а результатом является lvalue, ссылающееся на объект или функцию, на которую указывает выражение.
Итак, согласно правилу [expr.unary#op-1], категория значений *reinterpret_cast<T2 *>(p)
— это lvalue. И правило [expr.reinterpret.cast#11] требует только, чтобы тип исходного указателя мог быть преобразован в тип указателя назначения с помощью reinterpret_cast
, нет никаких других требований, таких как категория значения для его операнда. С этого момента я думаю, что Clang дает правильный процесс. Поскольку результатом является lvalue, следовательно, его можно привязать к ссылке lvalue.
Теперь немного модифицируем первый пример.
#include <iostream>
int main(){
int& rf = reinterpret_cast<int&>(1);
}
Затем три компилятора отвергают этот пример. Какого черта? Отличие от первого примера в том, что операндом reinterpret_cast
здесь является prvalue. Просто эта разница заставляет Clang
не принимать этот код? Согласно expr#10
Всякий раз, когда выражение prvalue появляется как операнд оператора, который ожидает значение gl для этого операнда, применяется временное преобразование материализации для преобразования выражения в значение x.
Как сказано в приведенном выше правиле, временное преобразование материализации должно применяться к prvalue 1
, чтобы сделать его glvalue. После этого преобразования я не вижу, чтобы этот пример имел какое-либо отличие от первого примера. Почему Clang
теперь отклонить его?
Продолжим рассмотрение третьего примера
#include <iostream>
int main(){
int&& rf = reinterpret_cast<int&&>(1);
}
На этот раз GCC
принять этот пример, в то время как два других компилятора сообщают об ошибке, результат здесь.
Если изменить объявление на int&& rf = reinterpret_cast<int&&>((int&&)1);
, то Clang
примет это.
Кроме того, в [expr.reinterpret.cast#11] говорится, что результат преобразования ссылочного типа в другой с использованием reinterpret_cast
всегда является lvalue (*reinterpret_cast<T2 *>(p)
), намекает, что мы не можем использовать ссылочный тип rvalue для привязки к результату. Не так ли?
Эти примеры придумываются, когда я читаю [expr.reinterpret.cast#11]. Разные компиляторы дают очень разные результаты. Это странно. Я также думаю, что [expr.reinterpret.cast#11] дает очень расплывчатую формулировку. В нем не говорилось о требовании категории значения операнда, когда мы даем ссылку rvalue или тип ссылки lvalue. Он просто говорит, что до тех пор, пока эти типы указателей могут быть преобразованы. В правиле также не указано, к какой категории значений относится результат. Мы можем сделать вывод, что его категория значений — это lvalue, только с помощью выражения *reinterpret_cast<T2 *>(p)
.
Как интерпретировать эти вопросы. Является ли это дефектом [expr.reinterpret.cast#11]?
Итак, согласно правилу [expr.unary#op-1], категория значений
*reinterpret_cast<T2 *>(p)
— это lvalue.
Нет, потому что [expr.reinterpret.cast]/11 не говорит, что выражение приведения ссылок во всех отношениях эквивалентно *reinterpret_cast<T2*>(p)
. Он говорит, что результат выражения такой же, как и у этого выражения. Здесь «результат» относится к [basic.lval]/5:
Результатом glvalue является объект, обозначенный выражением.
Категория значения выражения reinterpret_cast
определяется только [expr.reinterpret.cast]/1,
Результатом выражения
reinterpret_cast<T>(v)
является результат преобразования выраженияv
в типT
. ЕслиT
является ссылочным типом lvalue или ссылкой rvalue на тип функции, результатом является lvalue; еслиT
является ссылкой rvalue на тип объекта, результатом является значение x; в противном случае результатом является prvalue и ....
Таким образом, reinterpret_cast<int&>((int&&)1)
имеет категорию значений lvalue, потому что тип int&
является ссылочным типом lvalue, а не из-за унарного правила *
. Его результатом является объект, созданный временной материализацией prvalue 1
.
reinterpret_cast<int&>(1)
... преобразование временной материализации должно применяться к prvalue1
, чтобы сделать его glvalue
Нет — единственной частью [basic.reinterpret.cast], которая могла бы описать reinterpret_cast<int&&>(1)
, снова будет параграф 11 для преобразования glvalue в ссылочный тип. Но его последнее предложение включает в себя «Временное не создается», поэтому временная материализация не допускается.
Таким образом, и reinterpret_cast<int&>(1)
, и reinterpret_cast<int&&>(1)
имеют неправильный формат, а gcc неправильно принимает reinterpret_cast<int&&>(1)
без ошибок или предупреждений.
Соглашусь, не все так однозначно, как могло бы быть. Но интересно, что в параграфе 1 конкретно упоминаются обычные три преобразования glvalue в prvalue, но не обычное преобразование prvalue в xvalue.
Я думаю, что это следует рассматривать как дефект формулировки. Судя по поведению Clang
, ваше мнение верное. Однако, честно говоря, я не читаю тонкости из пункта 1 (как ваша интерпретация). Он также не говорит, что временное преобразование материализации разрешено или запрещено. Как правило, «всякий раз, когда выражение prvalue появляется как операнд оператора, который ожидает glvalue для этого операнда». Следовательно, я не знаю, что правило [expr.reinterpret.cast#11] говорит, ожидает ли оно значение gl или исходное выражение должно быть значением gl? какое здесь правильное намерение? Здесь неясно.
О, подождите. В пункте 11 говорится, что «временное не создается». Это более актуально, чем все остальное, что я цитировал, и я бы сказал, что оно определенно исключает временную материализацию.
Спасибо. Я думал, что временное означает между двумя разными типами, reinterprest_cast не может вызывать какое-либо определяемое пользователем преобразование для создания временного объекта целевого типа. Предположительно, временное - это ваш смысл. В любом случае, с Рождеством.
«Я думаю, подразумевается, что временное преобразование материализации никогда не применяется к операнду reinterpret_cast». Я так не думаю. Как сказано в пункте 1, категория значения определяется типом назначения. Смысл формулировки после
otherwise
означает, что если результатомreinterpret_cast
является prvalue, то к выражениюv
можно применить только эти перечисленные преобразования. Это не говорит о том, что только эти перечисленные преобразования могут применяться к выражениюv
в любом случае. В конце концов, все функции этих перечисленных преобразований используются для чтения значения для формирования prvalue.