Привязка lvalue к rvalue reference - g++ ошибка?
В качестве ответа на еще один вопрос я хотел опубликовать следующий код (то есть, я хотел бы опубликовать код на основе этой идеи):
#include <iostream>
#include <utility> // std::is_same, std::enable_if
using namespace std;
template< class Type >
struct Boxed
{
Type value;
template< class Arg >
Boxed(
Arg const& v,
typename enable_if< is_same< Type, Arg >::value, Arg >::type* = 0
)
: value( v )
{
wcout << "Generic!" << endl;
}
Boxed( Type&& v ): value( move( v ) )
{
wcout << "Rvalue!" << endl;
}
};
void function( Boxed< int > v ) {}
int main()
{
int i = 5;
function( i ); //<- this is acceptable
char c = 'a';
function( c ); //<- I would NOT like this to compile
}
Однако, хотя MSVC 11.0 дросселируется при последнем вызове, так как он должен IHMO, MinGW g++ 4.7.1 просто принимает его и вызывает конструктор с формальным аргументом rvalue reference.
Мне кажется, что lvalue привязана к ссылке rvalue. Ответ glib может состоять в том, что lvalue преобразуется в rvalue. Но вопрос в том, является ли это ошибкой компилятора, и если это не так, как это разрешено Священным стандартом?
EDIT: мне удалось свести все это к следующему довольно короткому примеру:
void foo( double&& ) {}
int main()
{
char ch = '!';
foo( ch );
}
Не удается скомпилировать с MSVC 11.0, компилируется ли с MinGW 4.7.1, что правильно?
Ответы
Ответ 1
Предположительно вы согласны, что это действительно?
void foo( double ) {} // pass-by-value
int main()
{
char ch = '!';
foo( ch );
}
Там подразумевается преобразование от char
до double
, поэтому функция жизнеспособна.
То же самое в примере в вашем отредактированном вопросе, там неявное преобразование, которое создает временное (т.е. значение rvalue) и аргумент rvalue-reference, привязывается к этому временному. Вы можете сделать это преобразование явным, если хотите:
void foo( double&& ) {} // pass-by-reference
int main()
{
char ch = '!';
foo( double(ch) );
}
но в этом случае это ничего не изменит. Это было бы необходимо, если double
→ char
можно было бы явно преобразовать (например, для типов классов с явными конструкторами или явными операторами преобразования), но double
to char
является допустимым неявным преобразованием.
"Правило rvalue-reference не может привязываться к правилу lvalue", о котором вы думаете, относится к привязке T&&
к значению T
lvalue, и это правило не нарушается, потому что double&&
не привязать к char
, он связывается с временным, созданным неявным преобразованием.
Это правило существует не только для предотвращения ненужного дополнительного копирования, но и для устранения реальной проблемы безопасности, существовавшей с предыдущими правилами, см. http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2812.html
Изменить:. Было задано, желательно ли это поведение на рефлекторе комитета (см. DR 1414), и это было решено, что да, это поведение предназначено и является правильным. Один из аргументов, используемых для достижения этой позиции, заключался в том, что этот код более эффективен с текущими правилами:
std::vector<std::string> v;
v.push_back("text");
При существующих правилах временный std::string
создается неявным преобразованием, тогда вызывается std::vector<T>::push_back(T&&)
, а временное перемещается в вектор. Если эта перегрузка push_back
не была жизнеспособной для результата преобразования, тогда указанный выше код вызывал бы std::vector<T>::push_back(const T&)
, который вызывал бы копию. Существующие правила делают этот реальный прецедент более эффективным. Если правила говорят, что rvalue-refs не могут связываться с результатом неявных преобразований, вам нужно будет изменить код выше, чтобы получить эффективность перемещения:
v.push_back( std::string{"text"} );
IMHO нет смысла явно строить a std::string
, когда этот конструктор не является явным. Я хочу последовательное поведение от явных/неявных конструкторов, и я хочу, чтобы первый пример push_back
был более эффективным.
Ответ 2
Я не проверяю спецификацию, но, думаю, char
можно автоматически добавить в int
. Поскольку вы не можете назначить что-либо (это значение r), будет передано значение R для временной переменной типа int
(чтобы быть более явным для значения (int)c
).
Ответ 3
Я обнаружил, что N3290 (идентичный стандарту С++ 11) содержит ненормативный пример привязки double&&
к rvalue, сгенерированный из int
lvalue, и обновленная формулировка в §8.5.3
"Если T1 относится к T2, а ссылка является ссылкой rvalue, выражение инициализатора не должно быть lvalue."
Как сообщается, правила были разработаны, чтобы избежать неэффективного дополнительного копирования. Хотя я не вижу, как такое копирование невозможно оптимизировать. Во всяком случае, разумно ли обоснование или нет? и это, конечно, не кажется разумным эффектом! – разрешен следующий код и компилируется как с MSVC 11, так и с MinGW g++ 4.7:
struct Foo {};
struct Bar { Bar( Foo ) {} };
void ugh( Bar&& ) {}
int main()
{
Foo o;
ugh( o );
}
Таким образом, очевидно, что MSVC 11 ошибочен, если не разрешить преобразование lvalue → rvalue.
РЕДАКТИРОВАТЬ. Я узнал, что есть отчет о дефектах по этой проблеме, DR 1414. Вывод в феврале 2012 года заключался в том, что текущая спецификация поведения "правильная", по-видимому, в отношении того, насколько хорошо она отражает намерение. Однако, как сообщается, он по-прежнему обсуждается в комитете, предположительно, в отношении практичности намерения.