Подтвердить что ты не робот

С++ 0x rvalue ссылки и временные

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

Почему вызов 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 (edit: Thanks Sumant: этого не происходит в GCC 4.3-4.5). По крайней мере, g++ и MSVC, любое lvalue не связывается с аргументом ссылки rvalue, даже если есть промежуточный временный созданный. В самом деле, если перегрузка const ref отсутствует, компиляторы диагностируют ошибку. Тем не менее, запись f(arg + 0) или f(std::string(arg)) действительно выбирает опорную перегрузку rvalue, как и следовало ожидать.

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

Это:

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

EDIT: у меня есть следующий вопрос, который связан с: С++ 0x ссылки на rvalue - привязка lvalues-rvalue

4b9b3361

Ответ 1

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

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

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

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

За исключением неявного параметра объекта, для которого см. 13.3.1, стандартная последовательность конверсии не может быть сформирована, если она требует привязки ссылки lvalue к неконстанту к 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, и, следовательно, вторая функция жизнеспособна.

Ответ 2

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

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

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

  • [...]
  • В противном случае ссылка должна быть ссылкой lvalue на нестабильный тип const (т.е. 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) см. Ссылку.

Ответ 3

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

Ответ 4

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

Понятно, что ваша интуиция правильная... временные лица любого типа должны связываться с 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?)

Ответ 6

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

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