C ++ 0x rvalue ссылки и временные ссылки - PullRequest
14 голосов
/ 01 мая 2010

(я задал вариант этого вопроса на comp.std.c ++, но не получил ответа.)

Почему вызов f(arg) в этом коде вызывает перегрузку const 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 (редактирование: спасибо, Сумант: в 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 и ссылками на const в слишком многих местах. Кроме того, кажется, что правило, которое предотвращает привязку lvalues ​​к ссылкам rvalue (13.3.3.1.4 / 3), не должно применяться, если есть промежуточный временный объект - в конце концов, совершенно безопасно перейти от временного.

Это:

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

РЕДАКТИРОВАТЬ: У меня есть дополнительный вопрос, который связан с: C ++ 0x rvalue ссылки - привязка lvalues-rvalue

Ответы [ 6 ]

8 голосов
/ 01 мая 2010

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

  • Если ссылка является ссылкой lvalue, а выражение инициализатора - [тип lvalue / class] ...
  • В противном случае ссылка должна быть ссылкой lvalue на энергонезависимый тип const (т. Е. Cv1 должна быть const), или ссылка должна быть ссылкой rvalue, а выражение инициализатора должно быть значением rval или иметь тип функции.

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

Теперь при разрешении перегрузки напрямую не используется привязка ссылок, чтобы увидеть, существует ли неявная последовательность преобразования. Вместо этого это говорит в 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 запрещает привязывать битовые поля к ссылкам lvalue. Но разрешение перегрузки говорит о том, что последовательность преобразования - это та, которая преобразуется в int, и, таким образом, это происходит успешно, хотя, когда вызов выполняется позже, вызов неверно сформирован. Таким образом, мой пример битовых полей плохо сформирован. Если бы он выбрал версию B, он бы был успешным, но требовал преобразования, определенного пользователем.

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

За исключением неявного параметра объекта, для которого см. 13.3.1, стандартная последовательность преобразования не может быть сформирована, если она требует привязки ссылки lvalue к non-const к rvalue или привязки ссылки rvalue к lvalue.

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

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), вы создадите значение r, и, следовательно, вторая функция будет жизнеспособной.

6 голосов
/ 29 августа 2011

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

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

Исправление правила внесено в черновик n3225.pdf (2010-11-27):

  • [...]
  • В противном случае ссылка должна быть ссылкой lvalue на энергонезависимый тип const (т. Е. Cv1 должна быть константой), или ссылка должна быть ссылкой 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 (названные разработчиками MSVC «ссылки на rvalue 2.1») см. Ссылку .

2 голосов
/ 01 мая 2010

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

1 голос
/ 01 мая 2010

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

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

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

Кажется, снова проскальзывает сквозь трещины в §13.3.3.1 / 6: (выделено мной)

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

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

РЕДАКТИРОВАТЬ: На самом деле проблема не существует в моей версии GCC.Я все еще пытаюсь выяснить, как второе стандартное преобразование определенной пользователем последовательности преобразования связано с формированием ссылки на rvalue.(Является ли формирование RR преобразованием вообще? Или оно продиктовано разрозненными лакомыми кусочками, такими как 5.2.2 / 5?)

0 голосов
/ 01 мая 2010

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

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

0 голосов
/ 01 мая 2010

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

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

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

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

...