Странная проблема с преобразованием ссылочного типа в другой с помощью оператора reinterpret_cast

#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]?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
101
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Итак, согласно правилу [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) ... преобразование временной материализации должно применяться к prvalue 1, чтобы сделать его glvalue

Нет — единственной частью [basic.reinterpret.cast], которая могла бы описать reinterpret_cast<int&&>(1), снова будет параграф 11 для преобразования glvalue в ссылочный тип. Но его последнее предложение включает в себя «Временное не создается», поэтому временная материализация не допускается.

Таким образом, и reinterpret_cast<int&>(1), и reinterpret_cast<int&&>(1) имеют неправильный формат, а gcc неправильно принимает reinterpret_cast<int&&>(1) без ошибок или предупреждений.

«Я думаю, подразумевается, что временное преобразование материализации никогда не применяется к операнду reinterpret_cast». Я так не думаю. Как сказано в пункте 1, категория значения определяется типом назначения. Смысл формулировки после otherwise означает, что если результатом reinterpret_cast является prvalue, то к выражению v можно применить только эти перечисленные преобразования. Это не говорит о том, что только эти перечисленные преобразования могут применяться к выражению v в любом случае. В конце концов, все функции этих перечисленных преобразований используются для чтения значения для формирования prvalue.

xmh0511 23.12.2020 16:30

Соглашусь, не все так однозначно, как могло бы быть. Но интересно, что в параграфе 1 конкретно упоминаются обычные три преобразования glvalue в prvalue, но не обычное преобразование prvalue в xvalue.

aschepler 24.12.2020 14:48

Я думаю, что это следует рассматривать как дефект формулировки. Судя по поведению Clang, ваше мнение верное. Однако, честно говоря, я не читаю тонкости из пункта 1 (как ваша интерпретация). Он также не говорит, что временное преобразование материализации разрешено или запрещено. Как правило, «всякий раз, когда выражение prvalue появляется как операнд оператора, который ожидает glvalue для этого операнда». Следовательно, я не знаю, что правило [expr.reinterpret.cast#11] говорит, ожидает ли оно значение gl или исходное выражение должно быть значением gl? какое здесь правильное намерение? Здесь неясно.

xmh0511 24.12.2020 16:23

О, подождите. В пункте 11 говорится, что «временное не создается». Это более актуально, чем все остальное, что я цитировал, и я бы сказал, что оно определенно исключает временную материализацию.

aschepler 24.12.2020 22:22

Спасибо. Я думал, что временное означает между двумя разными типами, reinterprest_cast не может вызывать какое-либо определяемое пользователем преобразование для создания временного объекта целевого типа. Предположительно, временное - это ваш смысл. В любом случае, с Рождеством.

xmh0511 25.12.2020 02:41

Другие вопросы по теме