Запутанные сообщения об ошибках с именованными ссылками 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 это конечно именующий, его тип все еще 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; Я ищу объяснение того, что происходит «за кадром».

4

Решение

Рассмотрим эти три задания:

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 ​​являются узлом привязки для присваиваний и чтения из памяти с известной длительностью хранения.

Второй не скомпилируется — вы не можете назначить ссылку на результат (таким образом), вы должны использовать синтаксис присвоения ссылки для псевдоним существующее значение Впрочем, это прекрасно:

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

Два случая:

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&&,

0

Другие решения


По вопросам рекламы [email protected]