2010-05-01 2 views
13

временные конструкции (я задавал вариации этого вопроса на comp.std.C++, но не получил ответа.)C++ 0x RValue ссылки и

Почему вызов f(arg) в этом коде вызова Конст ref перегрузка f?

void f(const std::string &); //less efficient 
void f(std::string &&); //more efficient 

void g(const char * arg) 
{ 
    f(arg); 
} 

Моя интуиция говорит, что перегрузка f(string &&) должна быть выбрана, потому что arg потребности не должны быть преобразованы в временный независимо от того, что и временные совпадения ссылки RValue лучше, чем ссылки Lvalue.

Это не то, что происходит в GCC и MSVC (Редактировать: спасибо Sumant: этого не происходит в GCC 4.3-4.5). В по крайней мере G ++ и MSVC любое lvalue не связывается с аргументом ссылки rvalue, , даже если существует промежуточное временное создание. В самом деле, если перегрузка const ref отсутствует, компиляторы диагностируют ошибку. Тем не менее, запись f(arg + 0) или f(std::string(arg))делает выбирает ссылочную перегрузку rvalue, как и следовало ожидать.

Из моего чтения стандарта C++ 0x кажется, что неявное преобразование const char * в строку должно учитываться при рассмотрении, если f(string &&) жизнеспособна, так же, как при передаче аргументов const lvalue ref. В разделе 13.3 (разрешение перегрузки) не проводится различие между rvalue ref и ссылками на const во многих местах. Кроме того, кажется, что правило, которое препятствует lvalues ​​от привязки к rvalue-ссылкам (13.3.3.1.4/3), не должно применяться, если есть промежуточный временный - в конце концов, совершенно безопасно перемещаться из временного.

Является ли это:

  1. Me искажения/неправильно понимает стандарт, в котором реализуется поведением является предполагаемым поведением, и есть какая-то веская причина, почему мой пример должен вести себя так, как это делает?
  2. Ошибка, которую продавцы компилятора каким-то образом сделали? Или ошибка, основанная на общих стратегиях реализации? Или ошибка, например. GCC (где это правило привязки ссылки lvalue/rvalue было впервые реализовано), которое было скопировано другими поставщиками?
  3. Дефект в стандартном или непреднамеренном результате или что-то, что необходимо уточнить?

EDIT: У меня есть прослеживания на вопрос, который связан: C++0x rvalue references - lvalues-rvalue binding

+0

В данной ситуации, почему бы перегрузка была быстрее? Строковый объект должен быть создан так или иначе и привязан к нему ссылкой. – UncleBens

+0

@UncleBens: предположим, что функция хранит свой аргумент в некоторой глобальной переменной/членной переменной - если аргумент известен как подвижный, то он может быть перемещен в цель, а не скопирован. – Doug

ответ

8

GCC делает это неправильно в соответствии с FCD. FCD говорит в 8.5.3 о референции связывания

  • Если ссылка именующая ссылки и выражение инициализатора является [именующим/типом класса] ...
  • В противном случае, ссылка должна быть именующей ссылкой на (т. е. cv1 должен быть const), или ссылка должна быть ссылкой на rvalue, а выражение инициализатора должно быть rvalue или иметь тип функции.

Вашего случай для обращения к std::string && не соответствует ни одной из них, потому что инициализатор является именующего. Он не добирается до места, чтобы создать временное значение rvalue, потому что для этой пули bullet уже требуется rvalue.

Теперь разрешение перегрузки напрямую не использует ссылку привязки, чтобы увидеть, существует ли неявная последовательность преобразования. Вместо этого, он говорит на 13.3.3.1.4/2

Если параметр ссылочного типа не связан непосредственно с выражением аргумента, последовательность преобразования является одним требуется преобразовать выражение аргумента к базовому типу ссылки в соответствии с 13.3. 3.1.

Таким образом, разрешение перегрузки определяет победителя, хотя этот победитель фактически не сможет связать этот аргумент. Например:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); 
void f(B); 
int main() { A a; f(a.bits); } 

ссылка на связывании 8.5 запрещает битовые поля для связывания с именующей ссылкой. Но разрешение на перегрузку говорит о том, что последовательностью преобразования является преобразование в int, что приводит к успеху, хотя при последующем вызове вызов плохо сформирован. Таким образом, мой пример битовых полей плохо сформирован. Если бы он выбрал версию B, она бы преуспела, но потребовала определенного преобразования.

Однако, существует два исключения для этого правила. Они

для неявного параметра объекта, для которого увидеть 13.3.1 За исключением случаев, стандартная последовательность преобразования не может быть образованны, если он требует связывания именующей ссылки на неконстантный к RValue или связыванию ссылок на RValue именующий.

Таким образом, следующий вызов действует:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); /* binding an lvalue ref to non-const to rvalue! */ 
void f(B); 
int main() { A a; f(1); } 

И, таким образом, ваш пример называет версия const T&

void f(const std::string &); 
void f(std::string &&); // would bind to lvalue! 

void g(const char * arg) { f(arg); } 

Однако, если вы говорите f(arg + 0), вы создаете RValue, и таким образом, вторая функция жизнеспособна.

+0

Ах, я не читал 8.5.3 слишком близко, спасибо. Похоже, что правила привязки ссылок излишне строги - я не вижу, что есть какой-либо убедительный аргумент в пользу отклонения такого кода, но он может (неожиданно, ИМО) вызывать непреднамеренные копии - например. если вы вызвали вектор :: push_back с аргументом const char * lvalue. – Doug

+1

Еще одна вещь - запрет на non-const lvalue ref args, принимающий временные (для меня), специально для того, чтобы вы случайно не потеряли «лишнюю» информацию, которую функция могла бы возвращать через эту ссылку. Но это соображение не относится к rvalue ref, будь то const или non-const - это концептуально «не полезное значение» после вызова. Наконец, тот факт, что f (arg) не разрешен, но f (arg + 0) разрешен или что, например, stringvec.push_back (string (charptr)) является * более эффективным *, чем stringvec.push_back (charptr) для меня очень неинтуитивно. – Doug

+0

Этот ответ не обновлен, не так ли? Текущий проект требует, чтобы вторая перегрузка была выбрана без ошибок. – sellibitze

1

Много вещей в текущем проекте стандарта нужно разъяснить, если вы спросите меня. И компиляторы все еще развиваются, поэтому трудно доверять их помощи.

Понятно, что ваша интуиция правильная ... временные лица должны связываться с ссылками rvalue. Например, в §3.10 новый раздел «таксономия» категорически определяет временные значения как значения r.

Проблема может заключаться в том, что спецификация аргумента RR недостаточна для вызова создания временного. §5.2.2/5: «Если параметр является ссылочным типом const, при необходимости вводится временный объект». Это звучит подозрительно эксклюзивно.

Похоже, что проскальзывать через трещины в §13.3.3.1/6: (курсив мой)

Когда тип параметра не является ссылкой, неявные модели последовательности преобразования копия-инициализации параметра из выражения аргумента. Неявная последовательность преобразования - это та, которая необходима для преобразования выражения аргумента в prvalue типа параметра.

Обратите внимание, что копирование-инициализация string &&rr = "hello"; отлично работает в GCC.

EDIT: На самом деле проблема не существует в моей версии GCC. Я все еще пытаюсь понять, как второе стандартное преобразование пользовательской последовательности преобразования связано с формированием ссылки на rvalue. (Является ли образование RR преобразование вообще Или это продиктовано разбросанными лакомыми как 5.2.2/5?)

+0

Спасибо - я пропустил те предложения, которые вы цитировали. Кажется, они что-то путают. Кроме того, причина, по которой я не передал строковый литерал, заключается в том, что MSVC, по-видимому, относится к ним как lvalues, но GCC не делает - это довольно странно. – Doug

0

Взгляните на это:

http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx

RValue ссылка: разрешение перегрузки

Похоже, ваш случай: «Lvalues ​​настоятельно предпочитают привязку к ссылкам lvalue».

+0

Да, но на этом пути существует неявное временное преобразование, что делает его немного отличным от этих случаев. – Doug

2

Я не видел поведения, упомянутого Дугом в g ++. g ++ 4.5 и 4.4.3 оба вызывали f(string &&), как ожидалось, но VS2010 вызывает f(const string &). Какую версию g ++ вы используете?

+0

+1, мне жаль, что я не проверил проблему, прежде чем прыгать! – Potatoswatter

+0

Я переустановил в G ++, и вы правы - этого не происходит. Я уверен, что я проверил его там раньше! Похоже, что проблема может быть только в MSVC, или это неясно в стандарте. – Doug

0

Я не знаю, изменилось ли это в последних версиях стандарта, но он говорил что-то вроде «если есть сомнения, не используйте ссылку rvalue». Возможно, по соображениям совместимости.

Если вы хотите семантику перемещения, используйте f(std::move(arg)), который работает с обоими компиляторами.

6

Это был дефект в стандартной черновике, которую вы читали. Этот дефект попал в качестве побочного эффекта некоторых желающих редактирования, чтобы запретить привязку ссылок rvalue к lvalues ​​по соображениям безопасности.

Ваша интуиция правильная. Разумеется, нет никакого вреда в разрешении ссылки rvalue ссылаться на некоторые неназванные временные, даже если инициализатор был выражением lvalue. В конце концов, для этого нужны ссылки rvalue. Вопрос, который вы наблюдали, был исправлен в прошлом году. В следующем стандарте будет указано, что вторая перегрузка будет выбрана в вашем примере, где ссылка rvalue будет ссылаться на некоторый временный объект строки.

Правило исправление сделало его в проекте n3225.pdf (2010-11-27):

  • [...]
  • В противном случае, ссылка должна быть именующей ссылкой на (т. е. cv1 должен быть const), или ссылка должна быть ссылкой rvalue , а выражение инициализатора должно быть rvalue или иметь тип функции . [...]
    • [...]
    • В противном случае, временное из [...] создается [...]
 double&& rrd3 = i; // rrd3 refers to temporary with value 2.0 

Но N3225, кажется, пропустил, чтобы сказать, что i в этом примере.Последний проект N3290 содержит следующие примеры:

 double d2 = 1.0; 
     double&& rrd2 = d2; // error: copying lvalue of related type 
     int i3 = 2; 
     double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0 

Поскольку ваша версия MSVC был выпущен до этого проблема была исправлена, она по-прежнему обрабатывает ссылки RValue по старым правилам. Ожидается, что следующая версия MSVC будет внедрять новые правила ссылки rvalue (дублированные «rvalue references 2.1» разработчиками MSVC) see link.

Смежные вопросы