C++0x rvalue ссылки и временные ссылки
(Я задал вариант этого вопроса на comp.std.C++, но не получил ответа.)
Почему звонок f(arg)
в этом коде вызовите const ref overload 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 и ссылками на const в слишком многих местах. Кроме того, кажется, что правило, которое предотвращает привязку lvalues к ссылкам rvalue (13.3.3.1.4/3), не должно применяться, если есть промежуточный временный объект - в конце концов, совершенно безопасно перейти от временного.
Это:
- Я неправильно понимаю / неправильно понимаю стандарт, где реализованное поведение является предполагаемым поведением, и есть какая-то веская причина, почему мой пример должен вести себя так, как он?
- Ошибка, которую производители компиляторов как-то все сделали? Или ошибка, основанная на общих стратегиях реализации? Или ошибка, например, в GCC (где это правило привязки ссылки lvalue/rvalue было впервые реализовано), который был скопирован другими поставщиками?
- Дефект в стандарте, или непреднамеренное последствие, или что-то, что следует уточнить?
РЕДАКТИРОВАТЬ: у меня есть следующий вопрос, который связан: ссылки C++0x rvalue - привязка lvalues-rvalue
6 ответов
GCC делает это неправильно в соответствии с FCD. FCD говорит в 8.5.3
о привязке ссылки
- Если ссылка является ссылкой lvalue, а выражение инициализатора - [тип lvalue / class]...
- В противном случае ссылка должна быть ссылкой lvalue на энергонезависимый тип const (т. Е. Cv1 должна быть константой), или ссылка должна быть ссылкой 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)
, вы создаете rvalue, и, таким образом, вторая функция является жизнеспособной.
Это был дефект в стандартном черновике, который вы прочитали. Этот дефект стал побочным эффектом некоторого нетерпеливого редактирования, чтобы запретить привязку rvalue-ссылок к lvalues по соображениям безопасности.
Ваша интуиция верна. Конечно, нет ничего плохого в том, чтобы позволить ссылке на rvalue ссылаться на какое-то безымянное временное имя, даже если инициализатор был выражением lvalue. В конце концов, это то, для чего нужны ссылки на rvalue. Наблюдаемая вами проблема была исправлена в прошлом году. В следующем стандарте будет указано, что вторая перегрузка будет выбрана в вашем примере, где ссылка на rvalue будет ссылаться на некоторый временный строковый объект.
Исправление правила внесено в черновик n3225.pdf (2010-11-27):
- [...]
- В противном случае ссылка должна быть ссылкой lvalue на энергонезависимый тип const (т. Е. Cv1 должна быть константой), или ссылка должна быть ссылкой rvalue,
а выражение инициализатора должно быть значением rval или иметь тип функции. [...]
- [...]
- В противном случае создается временный [...] временный [...]
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"), см. Ссылку.
Я не видел поведение, упомянутое Дугом на g++. g++ 4.5 и 4.4.3 оба вызова f(string &&)
как и ожидалось, но VS2010 звонки f(const string &)
, Какую версию g ++ вы используете?
Многие вещи в нынешнем проекте стандарта нуждаются в уточнении, если вы спросите меня. И компиляторы все еще развиваются, поэтому трудно доверять их помощи.
Кажется, довольно ясно, что ваша интуиция верна... временные лица любого рода должны связываться со ссылками. Например, §3.10, новый раздел "Таксономия", категорически определяет временные значения как r-значения.
Проблема может заключаться в том, что спецификации аргумента RR недостаточно, чтобы вызвать создание временного объекта. §5.2.2/5: "Если параметр имеет константный ссылочный тип, при необходимости вводится временный объект". Это звучит подозрительно эксклюзивно.
Кажется, снова проскальзывает через трещины в §13.3.3.1/6: (выделено мной)
Когда тип параметра не является ссылкой, последовательность неявного преобразования моделирует инициализацию копирования параметра из выражения аргумента. Последовательность неявного преобразования - это та, которая требуется для преобразования выражения аргумента в значение типа параметра.
Обратите внимание, что копия-инициализация string &&rr = "hello";
отлично работает в GCC.
РЕДАКТИРОВАТЬ: На самом деле проблема не существует в моей версии GCC. Я все еще пытаюсь выяснить, как второе стандартное преобразование определенной пользователем последовательности преобразования связано с формированием ссылки на rvalue. (Является ли формирование RR преобразованием вообще? Или оно продиктовано разрозненными лакомыми кусочками, как 5.2.2/5?)
Я не знаю, изменилось ли это в последних версиях стандарта, но раньше он говорил что-то вроде "если сомневаетесь, не используйте ссылку на значение". Вероятно, из соображений совместимости.
Если вы хотите переместить семантику, используйте f(std::move(arg))
, который работает с обоими компиляторами.
Взгляните на это:
http://blogs.msdn.com/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx
rvalue ссылки: разрешение перегрузки
Похоже, ваш случай таков: "Lvalue настоятельно предпочитает привязку к ссылкам lvalue".