Может ли reinterpret_cast (или любое приведение) преобразовывать значения x в значения l?

Является ли следующий код допустимым (в соответствии со стандартами С++ 11 и/или С++ 14))?

#include <iostream>
#include <utility>

using namespace std;

void foo(int &a) {
    cout << a << endl;
}

int main() {
    foo(reinterpret_cast<int &>(move(5)));
}
  • Если да, то это неопределенное поведение?
  • Если это не неопределенное поведение, могу ли я даже изменить a внутри foo, не превратившись в UB?

Он компилируется на clang 3.5, а не на gcc 4.9. Ошибка GCC:

➤ g++-4.9 -std=c++1y sample.cpp -o sample                                                                                        
sample.cpp: In function 'int main()':
sample.cpp:11:40: error: invalid cast of an rvalue expression of type 'std::remove_reference<int>::type {aka int}' to type 'int&'
     foo(reinterpret_cast<int &>(move(5)));
                                        ^

РЕДАКТИРОВАТЬ

К вашему сведению, специально созданное приведение, менее сложное, чем предыдущее, и работающее на C++11 как для GCC, так и для Clang, будет следующей функцией lvalue:

#include <iostream>

namespace non_std {
    template <typename T>
    constexpr T &lvalue(T &&r) noexcept { return r; }
}

void divs(int &a, int &b) {
    int t = a;
    a /= b;
    b /= t;
}

int main() {
    using namespace std;
    using namespace non_std;

    int i_care_for_this_one = 4;
    divs(i_care_for_this_one, lvalue(2));
    cout << i_care_for_this_one << endl;
}

person pepper_chico    schedule 07.11.2014    source источник
comment
почему бы вам не попробовать скомпилировать его с помощью стандартного совместимого компилятора и посмотреть, что произойдет дальше?   -  person Raptor    schedule 07.11.2014
comment
@Raptor под стандартным совместимым компилятором Я имею в виду идеальный компилятор, который реализовал бы его без ошибок, открытый для неопределенного поведения компиляции только тогда, когда сам стандарт открыт для интерпретации.   -  person pepper_chico    schedule 07.11.2014
comment
Вы можете упомянуть сообщение об ошибке, которое вы получаете на GCC. Visual Studio 2013 говорит error C2102: '&' requires l-value.   -  person Retired Ninja    schedule 07.11.2014
comment
@RetiredNinja обновлен. ошибка msvc мне несколько чужда =D   -  person pepper_chico    schedule 07.11.2014
comment
@MattMcNabb Аргумент представляет собой значение x, следовательно, значение gl, а в последнем абзаце говорится, что значение gl может быть преобразовано в ссылку.   -  person Brian Bi    schedule 07.11.2014
comment
@ Брайан, спасибо, что изучил это! Пожалуйста, дайте ответ (если вы уверены ;-)).   -  person pepper_chico    schedule 07.11.2014
comment
@Brian: Аргумент представляет собой значение x, - неверно, если вы имеете в виду 5... это prvalue согласно 3.10/1. Значение литерала, такого как 12, 7.3e5 или true, также является цена. (Обратите внимание, что значения prvalue не являются значениями gl).   -  person Tony Delroy    schedule 07.11.2014
comment
@TonyD Он становится значением x после того, как стал moved.   -  person Brian Bi    schedule 07.11.2014
comment
Просто в качестве мысленного эксперимента - чего бы это ни стоило - std::move() - это библиотечная функция, получающая аргумент prvalue - обычно вы ожидаете, что значение int будет передано в регистр ЦП. И, с другой стороны, вы обычно ожидаете, что int& будет реализована как int*, поэтому мы рассматриваем необходимость того, чтобы move каким-то образом выделил адрес памяти для копирования значения. Я нахожу весьма примечательным, что clang, по-видимому, делает это... предполагает, что move() использует какой-то встроенный компилятор, который фактически запрашивает дополнительную запись int в стеке... смутно похоже на (фиксированную) alloca?   -  person Tony Delroy    schedule 07.11.2014
comment
@TonyD это то, что меня впечатлило с самого начала, потому что оно компилировалось и работало, хотя я понятия не имел, поддерживает это стандарт или нет.   -  person pepper_chico    schedule 07.11.2014
comment
@pepper_chico: да, могу понять, почему ты беспокоишься. Учитывая для lvalue, приведение ссылки reinterpret_cast<T&>(x) имеет тот же эффект, что и преобразование *reinterpret_cast<T*>(&x) со встроенными операторами & и * в сочетании с cppreference's для rvalue, включая prvalues ​​&std::move(val) недействительны, но я не могу найти стандартные кавычки поддерживая утверждение cppreference. Я думаю, что ответ Дитмара Кюля ближе всего к этому....   -  person Tony Delroy    schedule 07.11.2014
comment
@TonyD Печально, но это в разделе заметок, и он явно сужает пояснительный материал до lvalue. Кроме того, добавление Майкла Вонга, которое попало в стандарт, должно иметь цель, и я думаю, что код вопроса является примером применения добавленных расслабляющих правил: если он не может этого сделать, что может позволить это тонкое изменение?   -  person pepper_chico    schedule 07.11.2014
comment
@TonyD Разница на open-std.org/jtc1 /sc22/wg21/docs/cwg_defects.html#1268 похоже подтверждает мои рассуждения.   -  person pepper_chico    schedule 07.11.2014
comment
@TonyD, Кроме того, только что вспомнил, что литералы, принимаемые в параметрах const &T, не являются чем-то новым и никогда не были проблемой, так что это просто небольшое расширение, и Брайан хорошо процитировал [dcl.init.ref] в своем ответе.   -  person pepper_chico    schedule 07.11.2014
comment
Потому что литерал помещается в стек в пространстве параметров. поэтому на него можно взять ссылку, но константное приведение и изменение не изменит литерал. Я слышал, что у вас могут быть сбои (сигналы) при попытке изменить что-то в постоянной памяти на некоторых ОС. move не принимает prvalue, он принимает универсальную ссылку. Я не уверен, что это должно быть абсолютно законно, но мне нравится ваш вопрос. проголосовал за это.   -  person v.oddou    schedule 21.11.2014


Ответы (3)


Обновление: код имеет неправильный формат в C++11. Ответ ниже для С++ 14. См. примечание в конце этого ответа.

Я считаю, что этот код и правильно сформирован, и правильно определен. Вот почему.

Результатом std::move является xvalue [1], тип glvalue; и преобразование glvalue в ссылку lvalue с помощью reinterpret_cast, по-видимому, разрешено формулировкой стандарта:

Выражение glvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» можно явно преобразовать в тип «указатель на T2» с помощью reinterpret_cast. Результат ссылается на тот же объект, что и исходное glvalue, но с указанным типом. [Примечание: то есть для lvalue приведение ссылки reinterpret_cast<T&>(x) имеет тот же эффект, что и преобразование *reinterpret_cast<T*>(&x) со встроенными операторами & и * (и аналогично для reinterpret_cast<T&&>(x)). — примечание в конце] Временные файлы не создаются, копии не создаются, а конструкторы (12.1) или функции преобразования (12.3) не вызываются.73

Поскольку «указатель на int» может быть преобразован в «указатель на int», этот reinterpret_cast также разрешен. Стандарт ничего не говорит о том, должен ли целевой тип быть ссылкой lvalue или ссылкой rvalue.

Результат приведения четко определен абзацем выше: он ссылается на тот же объект, что и исходное значение glvalue, то есть на временный объект int со значением 5. ([dcl.init.ref] указывает, что временное создается, когда prvalue привязано к ссылке.)

Доступ к значению через int& также не нарушает никаких правил псевдонимов, поскольку исходный объект также имел тип int. На самом деле я считаю, что было бы даже четко определено изменить временное значение через полученное таким образом lvalue.

Примечание. В формулировке C++11 говорится "выражение lvalue", а не "выражение glvalue". Формулировка с выражением glvalue взята из N3936, которая является окончательным рабочим проектом для C++14. Я не эксперт в том, как работает процесс стандартизации, но я считаю, что это означает, что изменение «lvalue» на «glvalue» уже было одобрено комитетом, и когда ISO опубликует стандарт C++14, он будет быть очень похожим на то, что он говорит выше.

[1] За исключением редкого случая, когда аргумент является функцией; в этом случае результатом является lvalue, так как нет функций rvalue.

person Brian Bi    schedule 07.11.2014
comment
Я предполагаю, что ваш ответ правильный, пока не будет какого-то контраргумента для всех из них. - person pepper_chico; 07.11.2014
comment
Формулировка в ответе @hs_ кажется такой же, как в стандарте С++ 11 (я не могу проверить достоверность). Формулировка, которую вы упомянули, находится в проекте github, что я не уверен, такое же, как для стандарта С++ 14. Я запутался в этом и в том, является ли код законным в С++ 14, а не в С++ 11. (хотя clang по-прежнему компилируется в режиме C++11) - person pepper_chico; 07.11.2014
comment
Спасибо, я приму этот ответ. Откажусь от выбора только в том случае, если кто-то скажет мне, что окончательный финальный ISO C ++ 14 был волшебным образом изменен. - person pepper_chico; 07.11.2014
comment
В ответе нет упоминания о времени жизни объекта. Будет плюс, если вы упомянете об этом. - person Euri Pinhollow; 28.01.2018
comment
CWG1268 был принят в качестве аварийного восстановления, поэтому он распространяет изменение формулировки обратно на C++11. - person Language Lawyer; 18.11.2020

Проблема заключается в том, разрешено ли reinterpret_cast преобразовывать xvalue в lvalue. В отличие от того, что вставляют другие, в соответствующем параграфе (5.2.10.11) упоминаются только значения lvalue:

Выражение lvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» может быть явно преобразовано к типу «указатель на T2» с помощью reinterpret_cast..

Есть предложение Майкла Вонга изменить формулировку на glvalue, но, похоже, оно натолкнулось на стену:

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1268

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

person hs_    schedule 07.11.2014
comment
Спасибо за ссылку на отчет о дефекте! - person pepper_chico; 07.11.2014

Соответствующий раздел стандарта — 5.2.10 [expr.reinterpret.cast]. Есть два соответствующих абзаца:

Во-первых, есть параграф 1, который заканчивается:

Никакое другое преобразование не может быть выполнено явным образом с помощью reinterpret_cast.

... и параграф 11, так как ни один другой не применяется:

Выражение glvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» можно явно преобразовать в тип «указатель на T2» с помощью reinterpret_cast. Результат ссылается на тот же объект, что и исходное glvalue, но с указанным типом. [Примечание: то есть для lvalue приведение ссылки reinterpret_cast<T&>(x) имеет тот же эффект, что и преобразование *reinterpret_cast<T*>(&x) со встроенными операторами & и * (и аналогично для reinterpret_cast<T&&>(x)). — примечание в конце] Временное не создается, копия не делается, а конструкторы (12.1) или функции преобразования (12.3) не вызываются.

Все остальные предложения не применяются к объектам, а только к указателям, указателям на функции и т. д. Поскольку значение r не является значением gl, код недопустим.

person Dietmar Kühl    schedule 07.11.2014
comment
Как указывает Брайан, результатом std::move является значение x (которое является значением gl); [basic.lval/1] Результатом вызова функции, тип возвращаемого значения которой является ссылка на rvalue, является xvalue., а std::move версия с одним аргументом является такой функцией - person M.M; 07.11.2014
comment
@MattMcNabb также намеренно использует std::move, так как clang выдает ошибку без него (отсюда и вопрос). - person pepper_chico; 07.11.2014
comment
Насколько я вижу, код должен быть допустимым из-за movexvalueglvalue. Интересно, откуда std::remove_reference (без ссылки). - person Cheers and hth. - Alf; 07.11.2014
comment
@Cheersandhth.-Alf Я думаю, что gcc просто удаляет ссылку из std::remove_reference<int>::type&&, когда печатает сообщение об ошибке, что имеет смысл, поскольку формально нет выражений ссылочного типа. - person Brian Bi; 07.11.2014
comment
@Alf, это часть возвращаемого типа std::move - person M.M; 07.11.2014