Как вернуть пересылочные ссылки? - PullRequest
0 голосов
/ 14 сентября 2018

Какой самый естественный способ вернуть прямую (универсальную) ссылку?

struct B{
         template<class Ar> friend
/*   1*/ Ar&& operator<<(Ar&& ar, A const& a){...; return std::forward<Archive>(ar);}
         template<class Ar> friend 
/*OR 2*/ Ar& operator<<(Ar&& ar, A const& a){...; return ar;}
         template<class Ar> friend 
/*OR 3*/ Ar  operator<<(Ar&& ar, A const& a){...; return std::forward<Ar>(ar);}
         template<class Ar> friend 
/*OR 4*/ Ar  operator<<(Ar&& ar, A const& a){...; return ar;}
/*OR 5*/ other??
}

Я имею в виду случай, похожий на поток, который создается и сразу используется. Например:

dosomethign( myarchive_type{} << B{} << B{} << other_unrelated_type{} );

1 Ответ

0 голосов
/ 14 сентября 2018

Очень редко рекомендуется возвращать ссылку на rvalue.Редкими исключениями являются случаи, когда вы пишете функцию, логически эквивалентную std::forward.

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

const auto& foo = some_operation;

, если some_operation является выражением prvalue, время жизни временного объекта увеличивается до foo.Однако, если вместо этого some_operation:

foo( some_prvalue_expression )

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

Для типов, которые дешевы длядвигаться, решение простое:

struct B{
  template<class Ar> friend
  Ar operator<<(Ar&& ar, A const& a){...; return std::forward<Archive>(ar);}
}

Возвращается по ссылке, если передано lvalue, но по значению, если передано rvalue.

Теперь продление срока действия ссылки работает правильно.

Это требует, чтобы ваш тип был дешевым для перемещения;так что

myarchive_type{} << B{} << B{} << other_unrelated_type{};

приводит к перемещению myarchive_type{} 3 раза.

Если ваш тип недешев для перемещения, попробуйте просто заблокировать версию rvalue;отсутствие безопасности делает его не стоящим.

Для конкретного примера посмотрите на эту функцию:

template<class C>
struct backwards {
  C c;
  auto begin() const {
    using std::rbegin;
    return rbegin(c);
  }
  auto end() const {
    using std::rend;
    return rend(c);
  }
};
template<class C>
backwards(C&&) -> backwards<C>;

(Извините, если я неправильно понял руководство по выводам).

Теперь мы делаем это:

for( auto x : backwards{ std::vector<int>{1,2,3} } ) {
  std::cout << x << ",";
}

vector перемещается в backwards, но если мы делаем это:

auto vec = std::vector<int>{1,2,3};
for( auto x : backwards{ vec } ) {
  std::cout << x << ",";
}

копия не создается.

Без «создать копию и вернуть ее, если нам передали значение r», первый цикл for вместо него содержит висячую ссылку.


Я видел несколько предложений илиидеи для предложений, чтобы попытаться обобщить продление срока действия ссылки или, по крайней мере, предоставить компиляторам подсказки, что возвращаемое значение функции зависит от времени жизни переданных ей аргументов, чтобы она могла генерировать ошибки / предупреждения при использовании висячей ссылкипуть.Никто, насколько я знаю, не сделал это стандартом C ++.

До тех пор, пока не сделаешь это, случайный возврат ссылки на rvalue просто слишком опасен для моей крови.рассмотреть вопрос о переезде дорогоХорошо, это работает:

std::invoke([&](auto&& ar){
  dosomething( ar << << B{} << B{} << other_unrelated_type{} );
}, myarchive_type{} );

он берет временный myarchive_type{} и сохраняет его в качестве ссылки rvalue.

Затем он передает его в лямбду.Внутри лямбды имя ar является lvalue.Мы переходим к безопасному использованию в лямбде.

...