Ссылка Rvalue рассматривается как Lvalue?

Я опубликовал этот ответ: https://stackoverflow.com/a/28459180/2642059, который содержит следующий код:

void foo(string&& bar){
    string* temp = &bar;

    cout << *temp << " @:" << temp << endl;
}

bar - это rvalue или lvalue?

Я спрашиваю, потому что я, очевидно, не могу принять адрес rvalue, но могу взять адрес ссылки rvalue, как это сделано здесь.

Если вы можете выполнить любую операцию со ссылкой rvalue, которую вы можете выполнить со ссылкой lvalue, то какой смысл различать их с помощью «&&», а не просто «&»?


person Jonathan Mee    schedule 12.02.2015    source источник
comment
Вся суть rvalues ​​состоит в том, чтобы разрешить семантику перемещения, они в большинстве случаев (кроме своего типа) являются просто ссылками.   -  person Guvante    schedule 12.02.2015
comment
Ответ @Guvante Майка Сеймура помог мне понять это. Но, как и в случае с комментарием, который я ему отправил, не вызовет ли это проблем, если мы будем использовать его для перемещения конструкции дважды? Скажите: string temp1(bar), temp2(bar);   -  person Jonathan Mee    schedule 12.02.2015
comment
В вашем примере вы не перемещаетесь дважды, вы копируете дважды, поскольку bar является lvalue (его тип - rvalue, странно, что я знаю). Однако если вы дважды позвонили std::move, у вас могут возникнуть проблемы.   -  person Guvante    schedule 12.02.2015
comment
@JonathanMee И это двойное использование bar именно объясняет, почему bar является lvalue - чтобы такое двойное использование не доставляло вам проблем. Он обрабатывается как rvalue только тогда, когда вы делаете это явно: std::move(bar).   -  person Angew is no longer proud of SO    schedule 12.02.2015
comment
@ Гуванте Секундочку? Ссылка rvalue - это lvalue, поэтому она не будет автоматически использовать конструктор move? Я просто не понимаю. Тогда почему мы вообще называем это ссылкой на rvalue? Единственное, что я думал, это требовало построения хода. Но если даже этого не сделать ...   -  person Jonathan Mee    schedule 12.02.2015
comment
@JonathanMee: Ты думаешь не по ту сторону забора. Ссылка rvalue перемещается в, а не перемещается из. Вы знаете, что когда ваша функция вызывается bar, будет уничтожена, это то, что вы получите. Однако, если вы затем вызываете функцию внутри себя, вы не даете этой гарантии, если вы явно не говорите об этом.   -  person Guvante    schedule 12.02.2015
comment
Тогда почему мы вообще называем это ссылкой на rvalue? - потому что это ссылка на rvalue (или педантично, на объект, обозначенный выражением rvalue). Это означает, что он относится к временной или переменной, из которой явно перемещается (с использованием std::move или аналогичного), поэтому ваша функция может предположить, что можно перейти от нее.   -  person Mike Seymour    schedule 12.02.2015
comment
Если вы можете выполнить какую-либо операцию со ссылкой на rvalue, я предлагаю вам быть осторожным с терминологией здесь: name, которое относится к переменной lvalue-reference имеет в качестве выражения семантику, идентичную name, которое относится к переменной rvalue-reference . Другими словами, семантика имени как выражения не зависит от ссылки на переменную, на которую ссылается имя. Что-то вроде static_cast<int&&>(42) по-прежнему является выражением-rvalue, тогда как let int x = 42; then static_cast<int&>(42) является выражением lvalue.   -  person dyp    schedule 12.02.2015
comment
Я думаю, что тонкость здесь в том, что lvalue / rvalue - это нечто, выходящее за рамки типа, это еще один атрибут объекта, а rvalue определяется не просто объявлением, а неявным созданием временного объекта или вызовом std :: move.   -  person TingQian LI    schedule 15.11.2018


Ответы (4)


bar - это rvalue или lvalue?

Ответ на вопрос сам собой. Все, что имеет имя, имеет lvalue (1). Итак, bar - это lvalue. Его тип - "ссылка rvalue на string", но это lvalue этого типа.

Если вы хотите рассматривать его как rvalue, вам нужно применить к нему std::move().


Если вы можете выполнить любую операцию со ссылкой rvalue, которую вы можете выполнить со ссылкой lvalue, то какой смысл различать их с помощью «&&», а не просто «&»?

Это зависит от вашего определения «выполнить операцию». Ссылка lvalue и (именованная) ссылка rvalue в значительной степени идентичны в том, как вы можете использовать их в выражениях, но они различаются во многом тем, что может связываться с ними. Lvalues ​​могут связываться со ссылками lvalue, rvalue могут связываться со ссылками rvalue (и все может связываться со ссылкой lvalue на const). То есть вы не можете привязать rvalue к ссылке lvalue или наоборот.

Давайте поговорим о параметре функции ссылочного типа rvalue (например, о вашем bar). Важно не то, что такое bar, а то, что вы знаете о значении, к которому относится bar. Поскольку bar является ссылкой rvalue, вы наверняка знаете, что все, что с ней связано, было rvalue. Это означает, что оно обязательно будет уничтожено, когда закончится полное выражение, и вы можете безопасно рассматривать его как rvalue ( путем кражи его ресурсов и т. д.).

Если не вы делаете это напрямую с bar, а просто хотите передать bar, у вас есть два варианта: либо вы закончили с bar, а затем вы должны сообщить следующему, кто его получит, что он привязан к rvaluedo std::move(bar). Или вам нужно будет сделать еще кое-что с bar, и поэтому вы не хотите, чтобы кто-либо в промежутке крал его ресурсы из-под вас, поэтому просто рассматривайте это как lvaluebar.

Подводя итог: Разница не в том, что вы можете делать со ссылкой , когда она у вас есть. Разница в том, что можно привязать к ссылке.


(1) Хорошее практическое правило с небольшими исключениями: у счетчиков есть имена, но они являются r-значениями. Классы, пространства имен и шаблоны классов имеют имена, но не значения.

person Angew is no longer proud of SO    schedule 12.02.2015
comment
@Agnew Что вызовет вторую часть моего вопроса, тогда какой смысл называть это ссылкой rvalue? Почему бы просто не вызвать ссылки rvalue и lvalue: ссылки? - person Jonathan Mee; 12.02.2015
comment
@JonathanMee Пытался объяснить. Пожалуйста, дайте мне знать, если это яснее или нужно больше. - person Angew is no longer proud of SO; 12.02.2015
comment
@JonathanMee - это ссылка на что-то, что можно безопасно рассматривать как rvalue (то есть вы можете вызвать std::move для него, и вы не рискуете сделать недействительным объект, который, как ожидается, в другом месте останется действительным). - person jalf; 12.02.2015
comment
@jalf Вы говорите, что это всего лишь намек для программиста, что он может move это сделать, если захочет? Тогда я просто не понимаю смысла различать ссылку на rvalue и lvalue. Вы также можете move lvalues, вам просто нужно знать, что вы делаете. - person Jonathan Mee; 12.02.2015
comment
@JonathanMee Нет, это не просто подсказка программисту. Это жесткое правило для внешней стороны. Разница не в том, что вы можете делать со ссылкой , когда она у вас есть. Разница в том, что что может привязать к ссылке. - person Angew is no longer proud of SO; 12.02.2015
comment
@Angew Спасибо, что остались со мной в этом вопросе. После того, как вы сказали, что все встало на свои места. - person Jonathan Mee; 12.02.2015
comment
Все, что имеет имя, является l-значением. Педантичное замечание: счетчики - это r-значения. Есть ли простое правило, которое примирит это? (например, имена переменных и функций являются l-значениями) - person dyp; 12.02.2015
comment
@dyp Всегда должны быть раздражающие подробности, верно? Изменен. - person Angew is no longer proud of SO; 12.02.2015
comment
@dyp: Официальное определение достаточно простое: lvalue обозначает функцию или объект. Имена не имеют значения: не все функции и объекты имеют имена, и не все именованные объекты имеют lvalue. - person Mike Seymour; 13.02.2015
comment
@MikeSeymour Исходя из этого определения, почему std::string{} не является lvalue? - person Angew is no longer proud of SO; 13.02.2015
comment
@Angew: он не обозначает существующий объект, а создает временный объект. Преобразование, которое создает временное значение, определяется как rvalue. - person Mike Seymour; 13.02.2015
comment
@MikeSeymour Форма этого утверждения (которая находится в части Стандарта, которую я считаю скорее информативной, чем нормативной): lvalue = ›обозначает функцию / объект. Я бы хотел иметь какое-то отношение X = ›lvalue. Например. в чем разница по отношению к обозначению объекта между f().member и g().member, где class_type& f() vs class_type&& g() (или даже class_type g())? - person dyp; 13.02.2015
comment
Относительно первого абзаца: тип выражения bar - std::string; тип объекта bar - std::string&&. Поскольку вы также говорите, что это lvalue, вы, кажется, говорите о выражении, и поэтому его тип на самом деле просто std::string. (Выражения никогда не имеют ссылочного типа) - person M.M; 05.07.2018
comment
Итак, у @SamWong был интересный комментарий: stackoverflow.com/a/51177761/2642059 Есть ли ответ на вопрос, почему bar будет сообщить, что это ссылка на lvalue? - person Jonathan Mee; 09.07.2018

Является ли bar rvalue или lvalue?

Это lvalue, как и любое выражение, называющее переменную.

Если вы можете выполнить какую-либо операцию со ссылкой rvalue, которую вы можете со ссылкой на lvalue, то какой смысл в различении между ними с помощью "&&" вместо просто " & "?

Вы можете инициализировать ссылку rvalue только выражением rvalue. Таким образом, вы можете передать своей функции временную строку, но вы не можете передать строковую переменную, не перейдя от нее явно. Это означает, что ваша функция может предположить, что аргумент больше не нужен, и может перейти от него.

std::string variable;
foo(variable);            // ERROR, can't pass an lvalue
foo(std::move(variable)); // OK, can explicitly move
foo("Hello");             // OK, can move from a temporary
person Mike Seymour    schedule 12.02.2015
comment
Итак, вы говорите, что смысл ссылки rvalue состоит в том, чтобы сообщить компилятору, что, когда он использует это значение в конструкторе, он может использовать конструктор перемещения? Разве это не вызовет проблем, если я инициализирую дважды? Скажите string temp1(bar), temp2(bar); - person Jonathan Mee; 12.02.2015
comment
@JonathanMee: Нет, ссылка на rvalue заключается в том, что она может связываться только с rvalue. В этом примере bar является lvalue, поэтому обе переменные temp инициализируются конструктором копирования (с использованием ссылки lvalue), а не конструктором перемещения (с использованием ссылки rvalue), что предотвращает любые проблемы, которые могут возникнуть, если первый конструктор переместится из bar. - person Mike Seymour; 12.02.2015

В данном случае именованные ссылки rvalue - это lvalue, а если тип ввода - const именованные ссылки rvalue, тогда он будет rvalue, вот простой пример:

#include <string>
#include <iostream>

void foo(const std::string&& bar) { 
    std::string* temp = &bar; // compile error, can't get address
    std::cout << *temp << " @:" << temp << std::endl;
}

int main()
{
    foo("test");
    return 0;
}

Надеюсь, это будет полезно.

person Kehe CAI    schedule 05.12.2017
comment
Так что, похоже, это правда. Можете ли вы предоставить источник по этому поводу? - person Jonathan Mee; 05.12.2017
comment
codeynthesis.com/~boris/blog//2012 / 07/24 / , надеюсь, что эта ссылка будет полезна - person Kehe CAI; 06.12.2017
comment
Это не правильно. bar здесь по-прежнему lvalue; проблема в том, что &bar имеет тип const std::string *, поэтому его нельзя присвоить std::string*, который отбросит квалификатор const. Если вы измените код на const std::string* temp =, он отлично скомпилируется. - person M.M; 05.07.2018

Выражение bar - это lvalue. Но это не «ссылка rvalue на строку». Выражение bar является ссылкой на lvalue. Вы можете проверить это, добавив следующую строку в foo ():

cout << is_lvalue_reference<decltype((bar))>::value << endl; // prints "1" 

Но я согласен с остальными объяснениями Энджью.

Ссылка rvalue после того, как она была привязана к rvalue, является ссылкой lvalue. На самом деле это касается не только параметров функции:

string&& a = "some string";
string&& b = a; // ERROR: it does not compile because a is not an rvalue

Если вы можете выполнить любую операцию со ссылкой rvalue, которую вы можете выполнить со ссылкой lvalue, то какой смысл различать их с помощью «&&», а не просто «&»?

Ссылка на rvalue позволяет нам выполнять некоторые «операции lvalue» до истечения срока действия rvalue.

person Sam Wong    schedule 04.07.2018
comment
Чтобы быть ясным: определение типа бара неоднозначно в его нынешнем виде. Токен bar синтаксически может быть выражением или не выражением. Если это синтаксически выражение, то это тип std::string (не ссылка). Однако, если это синтаксически операнд decltype(id-expression), тогда тип std::string&&. - person M.M; 05.07.2018