Запутанные сообщения об ошибках с именованными ссылками rvalue
Учтите следующее:
struct my_type {};
my_type make_my_type() { return my_type{}; }
void func(my_type&& arg) {}
int main()
{
my_type&& ref = make_my_type();
func(ref);
}
Излишне говорить, что этот код не компилируется. Я понимаю, что мне нужно использовать std::move()
во втором вызове функции, но в целях понимания я хочу рассмотреть код как есть.
Пытаясь скомпилировать вышесказанное, Clang 3.5 говорит мне:
ошибка: нет подходящей функции для вызова func
примечание: функция-кандидат недопустима: нет известного преобразования из my_type в my_type && для 1-го аргумента void func(my_type&&) {}
В то время как g++ 4.9 говорит что-то почти идентичное:
ошибка: невозможно связать значение my_type со значением my_type &&
примечание: инициализация аргумента 1 'void func(my_type&&)'
Эти сообщения об ошибках меня запутали, потому что пока ref
безусловно, lvalue, его тип по-прежнему my_type&&
... не так ли?
Я пытаюсь точно понять, что здесь происходит, поэтому мне интересно, какие (если таковые имеются) из следующего являются правдой:
Поскольку только rvalue могут быть связаны с rvalue ссылками, и
ref
это значение, оно не может быть связано сarg
, Сообщения об ошибках от Clang и g ++ вводят в заблуждение, утверждая, чтоref
является (не ссылка)my_type
что "не может быть преобразовано".Потому что это lvalue,
ref
рассматривается для разрешения перегрузки как нереференсныйmy_type
несмотря на его фактический типmy_type&&
, Сообщения об ошибках от Clang и g ++ вводят в заблуждение, потому что они отображают тип, используемый внутри для сопоставления функций, а не реальный типref
,В теле
main()
, типref
простоmy_type
несмотря на то, что я прямо написалmy_type&&
, Так что сообщения об ошибках от компиляторов точны, и я ошибаюсь. Однако, похоже, это не так, посколькуstatic_assert(std::is_same<decltype(ref), my_type&&>::value, "");
проходит.
Происходит какая-то другая магия, которую я не учел.
Просто повторюсь, я знаю, что решение заключается в использовании std::move()
преобразовать rref обратно в rvalue; Я ищу объяснение того, что происходит "за кадром".
1 ответ
Рассмотрим эти три задания:
my_type x = func_returning_my_type_byvalue();
my_type & y = func_returning_my_type_byvalue();
my_type && z = func_returning_my_type_byvalue();
Первое - у вас есть локальная переменная x
и он инициализируется в результате вызова функции (rvalue), так что можно использовать конструктор перемещения / присваивание или конструкцию x можно полностью исключить (пропуская и x
построен на месте func_returning_my_type_byvalue
когда он генерирует свой результат).
Обратите внимание, что x
является lvalue - вы можете взять его адрес, поэтому он также является типом ссылки. Технически все переменные, которые не являются ссылками, являются ссылками на себя. В этом отношении lvalues являются узлом привязки для присваиваний и чтения из памяти с известной длительностью хранения.
Второе не скомпилируется - вы не можете назначить ссылку на результат (таким образом), вы должны использовать синтаксис назначения ссылок для псевдонима существующего lvalue. Впрочем, это прекрасно:
my_type & y = func_returning_my_type_byreference();
// `y` will never use constructors or destructors
Вот почему существует третий, когда нам нужна ссылка на что-то, что мы не можем создать ссылку на использование обычного синтаксиса. В чем-то вроде func
в первоначальном вопросе, время жизни arg
не сразу очевидно. Например, мы не можем сделать это без явного перемещения:
void func( my_type && arg ) {
my_type && save_arg = arg;
}
Причина, по которой это не разрешено, заключается в том, что arg
это ссылка на значение в первую очередь. Если хранение arg
ценность (то, что это имеет в виду) должны быть короче, чем save_arg
, затем save_arg
вызвал бы деструктор этого значения - фактически захватывая его. Это не тот случай, save_arg
сначала исчезнет, поэтому нет смысла переводить в него lvalue, что мы можем, после func
, еще обращайся потенциально!
Учтите, что даже если бы вы использовали std:move
заставить это скомпилировать. Деструктор все равно не будет вызван func
потому что вы не создали новый объект, просто новую ссылку, а затем эта ссылка уничтожается до того, как сам исходный объект вышел из области видимости.
Для всех намерений и целей arg
ведет себя так, как будто это my_type&
Как и любые другие ссылки. Хитрость заключается в продолжительности хранения и семантике продления срока службы путем передачи ссылки. Это все обычные ссылки под капотом, здесь нет "типа значения".
Если это поможет, вспомните операторы увеличения / уменьшения. Существуют две перегрузки, а не два оператора. operator++(void)
(предварительно) и operator++(int)
(сообщение). Там никогда не бывает актуальным int
Проходя мимо, компилятор имеет разные подписи для разных ситуаций / контекстов / соглашений о обработке значений. Это своего рода сделка со ссылками.
Если ссылки на rvalue и lvalue всегда называются lvalue, в чем разница?
Одним словом: время жизни объекта.
Ссылка lvalue всегда должна назначаться для использования чего-то с большей продолжительностью хранения, того, что уже создано. Таким образом, нет необходимости вызывать конструкторы или деструкторы для области видимости ссылочной переменной lvalue, потому что по определению мы получаем готовый объект и забываем о нем до того, как он должен быть уничтожен.
также важно, чтобы объекты неявно уничтожались в обратном порядке:
int a; // created first, destroyed last
int b; // created second, destroyed 2nd-last
int & c = b; // fine, `c` goes out of scope before `b` per above
int && d = std::move(a); // fine, `a` outlives `d`, same situation as `c`
Если мы присвоили ссылку на rvalue, что-то, что является ссылкой на lvalue, применяется то же правило: lvalue должно по определению иметь более длительное хранилище, поэтому нам не нужно вызывать конструкторы или деструкторы для c
или даже d
, Вы не можете обмануть компилятор с std::move
на этом, потому что он знает область объекта, который перемещается - d
однозначно короче, чем указанная ссылка, мы просто заставляем компилятор использовать проверку / контекст типа rvalue, и это все, чего мы достигли.
Разница в том, что ссылки не имеют значения - например, выражения, в которых могут быть ссылки на них, но эти ссылки определенно недолговечны, возможно, короче, чем продолжительность локальной переменной. Подсказка Подсказка.
Когда мы присваиваем результат вызова функции или выражения для ссылки rvalue, мы создаем ссылку на временный объект, на который иначе нельзя было бы ссылаться. В связи с этим мы фактически заставляем на месте конструировать переменную из результата выражения. Это вариант разрешения копирования / перемещения, когда у компилятора нет другого выбора, кроме как исключить временную конструкцию на месте:
int a = 2, b = 3; // lvalues
int && temp = a + b; // temp is constructed in-place using the result of operator+(int,int)
Случай с func
Это сводится к присваиванию lvalue - ссылки в качестве аргументов функции относятся к объектам, которые могут существовать дольше, чем вызов функции, и, таким образом, являются lvalue, даже если тип аргумента является ссылкой на rvalue.
Два случая:
func( std::move( variable ) ); // case 1
func( my_type() + my_type() ); // case 2
func
Нельзя угадывать, в какой ситуации мы будем его использовать заранее (без оптимизации). Если бы мы не допустили случай 1, то была бы законная причина считать ссылочный параметр rvalue имеющим меньшую продолжительность хранения, чем вызов функции, но это также не имело бы смысла, поскольку любой объект всегда очищался внутри func
или всегда вне его, и "неизвестная" продолжительность хранения во время компиляции не является удовлетворительной.
У компилятора нет другого выбора, кроме как предположить худшее, что случай 1 может произойти в конце концов, и в этом случае мы должны дать гарантии на срок хранения arg
как дольше, чем вызов func
в общем случае. Как следствие этого - это arg
будет считаться, что существует дольше, чем призыв к func
какое -то время, и это func
сгенерированный код должен работать в обоих случаях - arg
допустимое использование и предполагаемая продолжительность хранения соответствуют требованиям my_type&
и не my_type&&
,