Информация об изменениях

Сообщение Re[4]: MSVC: A non-const reference may only be bound to an l от 26.07.2023 12:53

Изменено 26.07.2023 12:54 Sm0ke

Re[4]: MSVC: A non-const reference may only be bound to an lvalue
Здравствуйте, so5team, Вы писали:

S>В случае же когда параметр передается по ссылке, мы просто возвращаем эту же самую ссылку и на нее действуют обычные правила жизни.


S>Когда мы имеем дело с rvalue reference, то rvalue reference должно оставаться валидным в течении жизни выражения, в котором эта ссылка появилась. Т.е.:

S>
S>T && operator<<(T && d, int v) { ...; return std::move(d); }

S>(T{} << 0) << 1;
S>

S>сперва у нас создается временный объект T и rvalue reference на него передается в первый вызов operator<<. Он эту же самую ссылку возвращает. И эта ссылка остается валидной, т.к. выражение у нас не завершилось. Когда вызывается второй operator<<, то эта ссылка отдается туда же. И там она опять же валидна, т.к. выражение все еще не завершилось.

Вы ошибаетесь. Рассмотрим пример:

#include <utility>
#include <iostream>

struct obj_t {
    bool f = false;
    
    obj_t() { std::cout << "obj_t default\n"; }
    ~obj_t() { std::cout << "~obj_t\n"; }

    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
};

obj_t && some_fn(obj_t && o) { return std::move(o); } // bad

int main() {
  obj_t && tmp = some_fn( obj_t{} ); // tmp битая ссылка
  std::cout << "going to return\n";
  return 0;
}


Результат:

obj_t default
~obj_t
going to return


Время жизни временного объекта продлевается при привязке его на rvalue reference локальной переменной (или параметра) и откладывается до момента, когда переменная выйдет из scope. В данном случае время жизни параметра заканчивается после выхода из функции.
Иначе пример выше выдал бы сперва "going to return" из main(), а потом "~obj_t".

Тобишь когда выражение { obj_t && tmp = some_fn( obj_t{} ); } отработает, то tmp будет dangling ref.
При бинде одной rvalue ref от другой rvalue ref время жизни временного объекта дальше не продлевается. Только при бинде от самого временного объекта.

Корректный пример:

#include <utility>
#include <iostream>

struct obj_t {
    bool f = false;

    obj_t() { std::cout << "obj_t default\n"; }
    ~obj_t() { std::cout << "~obj_t\n"; }

    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
};

obj_t some_fn(obj_t && o) { return std::move(o); } // good

int main() {
  obj_t && tmp = some_fn( obj_t{} );
  std::cout << "going to return\n";
  return 0;
}


Результат:

obj_t default
obj_t move
~obj_t
going to return
~obj_t


К сожалению объект класса obj_t тут будет создан дважды.
Сперва default constructor, потом move constructor, потом дважды destructor.

В случае вектора — мув конструктор считается относительно дешёвым.
Re[4]: MSVC: A non-const reference may only be bound to an l
Здравствуйте, so5team, Вы писали:

S>В случае же когда параметр передается по ссылке, мы просто возвращаем эту же самую ссылку и на нее действуют обычные правила жизни.


S>Когда мы имеем дело с rvalue reference, то rvalue reference должно оставаться валидным в течении жизни выражения, в котором эта ссылка появилась. Т.е.:

S>
S>T && operator<<(T && d, int v) { ...; return std::move(d); }

S>(T{} << 0) << 1;
S>

S>сперва у нас создается временный объект T и rvalue reference на него передается в первый вызов operator<<. Он эту же самую ссылку возвращает. И эта ссылка остается валидной, т.к. выражение у нас не завершилось. Когда вызывается второй operator<<, то эта ссылка отдается туда же. И там она опять же валидна, т.к. выражение все еще не завершилось.

Вы ошибаетесь. Рассмотрим пример:

#include <utility>
#include <iostream>

struct obj_t {
    bool f = false;
    
    obj_t() { std::cout << "obj_t default\n"; }
    ~obj_t() { std::cout << "~obj_t\n"; }

    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
};

obj_t && some_fn(obj_t && o) { return std::move(o); } // bad

int main() {
  obj_t && tmp = some_fn( obj_t{} ); // tmp битая ссылка
  std::cout << "going to return\n";
  return 0;
}


Результат:

obj_t default
~obj_t
going to return


Время жизни временного объекта продлевается при привязке его на rvalue reference локальной переменной (или параметра) и откладывается до момента, когда переменная выйдет из scope. В данном случае время жизни параметра o заканчивается после выхода из функции some_fn() .
Иначе пример выше выдал бы сперва "going to return" из main(), а потом "~obj_t".

Тобишь когда выражение { obj_t && tmp = some_fn( obj_t{} ); } отработает, то tmp будет dangling ref.
При бинде одной rvalue ref от другой rvalue ref время жизни временного объекта дальше не продлевается. Только при бинде от самого временного объекта.

Корректный пример:

#include <utility>
#include <iostream>

struct obj_t {
    bool f = false;

    obj_t() { std::cout << "obj_t default\n"; }
    ~obj_t() { std::cout << "~obj_t\n"; }

    obj_t(obj_t &&) { std::cout << "obj_t move\n"; }
    obj_t(const obj_t &) { std::cout << "obj_t copy\n"; }
};

obj_t some_fn(obj_t && o) { return std::move(o); } // good

int main() {
  obj_t && tmp = some_fn( obj_t{} );
  std::cout << "going to return\n";
  return 0;
}


Результат:

obj_t default
obj_t move
~obj_t
going to return
~obj_t


К сожалению объект класса obj_t тут будет создан дважды.
Сперва default constructor, потом move constructor, потом дважды destructor.

В случае вектора — мув конструктор считается относительно дешёвым.