2014-11-07 9 views
8

Является ли следующий код легальным (по стандарту C++ 11 и/или C++ 14)?Может ли reinterpret_cast (или любое преобразование) преобразовать значения xvalues ​​в lvalues?

#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))); 
             ^

EDIT

FYI, выполненный на заказ литая, что меньше волосатые, чем предыдущий, и который работает на 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; 
} 
+0

Почему вы не пытаетесь скомпилировать его с помощью стандартного совместимого компилятора и посмотреть, что будет дальше? – Raptor

+0

@Raptor стандартным совместимым компилятором Я имею в виду идеальный, который бы реализовал его без ошибок, открыв неопределенное поведение компиляции только тогда, когда сам стандарт открыт для интерпретации. –

+1

Вы можете указать сообщение об ошибке, которое вы получите в GCC. Visual Studio 2013 говорит, что 'ошибка C2102: '&' требует l-value'. –

ответ

6

Обновление: Код плохо сформирован на C++ 11. Ниже приведен ответ для C++ 14. См. Примечание в конце этого ответа.

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

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

A glvalue expression of type T1 can be cast to the type “reference to T2 ” if an expression of type “pointer to T1 ” can be explicitly converted to the type “pointer to T2 ” using a reinterpret_cast . The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)). — end note ] No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.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, так как нет функций rvalues.

+0

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

+1

Формулировка в ответе @hs_ кажется одной в стандарте C++ 11 (я не могу проверить правдивость). Упомянутая вами формулировка [находится в проекте github] (https://github.com/cplusplus/draft/blob/master/source/expressions.tex#L2281), который я не уверен, такой же, как для C++ 14. Я смущен этим, и является ли код законным в C++ 14, а не на C++ 11. (clang все еще компилируется в режиме C++ 11) –

+0

Спасибо, я приму этот ответ. Отбросит выбор только в том случае, если кто-то скажет мне, что финальный окончательный ISO-код C++ 14 изменен. –

1

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

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

No other conversion can be performed explicitly using reinterpret_cast.

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

A glvalue expression of type T1 can be cast to the type “reference to T2 ” if an expression of type “pointer to T1 ” can be explicitly converted to the type “pointer to T2 ” using a reinterpret_cast . The result refers to the same object as the source glvalue, but with the specified type. [ Note: That is, for lvalues, a reference cast reinterpret_cast<T&>(x) has the same effect as the conversion *reinterpret_cast<T*>(&x) with the built-in & and * operators (and similarly for reinterpret_cast<T&&>(x)) . —end note ] No temporary is created, no copy is made, and constructors (12.1) or conversion functions (12.3) are not called.

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

+0

Как указывает Брайан, результатом 'std :: move' является значение xvalue (это значение glvalue); [basic.lval/1] «Результатом вызова функции, тип возврата которой является ссылка rvalue, является значение xvalue.», а версия «std :: move» с одним аргументом - это такая функция. –

+0

@MattMcNabb также: 'std: : move' там специально, так как clang предоставляет ошибку без него (отсюда и вопрос). –

+0

Насколько я вижу, код должен быть действительным из-за 'move' → * xvalue * → * glvalue *. Интересно, откуда берется «std :: remove_reference» (без ссылки). –

5

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

An lvalue expression of type T1 can be cast to the type “reference to T2” if an expression of type “pointer to T1” can be explicitly converted to the type “pointer to T2” using a reinterpret_cast..

Существует предложение, представленное Майкл Вонг, чтобы изменить формулировку glvalue, но это, кажется, врезался в стену:

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

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

+0

Thx для этого отчета о дефекте! –