Избегайте висячих ссылок для обратной реализации на основе цикла для цикла - PullRequest
0 голосов
/ 09 октября 2018

Фон и предыдущий поиск

Я ищу элегантный способ перебрать итерацию по контейнеру (например, std :: vector), используя основанный на диапазоне цикл for вC ++ 14.В поисках решения я нашел это Q / A .Это в основном говорит мне, что это не является частью стандартной библиотеки, и я должен использовать boost или реализовать адаптер самостоятельно.Я не хочу использовать boost, поэтому сейчас я ищу лучшую собственную реализацию.

Помимо предложений, приведенных в ранее упомянутых Q / A , я также нашел эта реализация и этот блог по этой теме.Большинство реализаций очень похожи и выглядят вполне прилично.Однако у всех них есть подводный камень: как указано в этом комментарии , вы можете получить висячую ссылку, если вызовете обратный адаптер с временным объектом:

for (const auto& v : reverse_iterate(getContainer()))

Относительнопроблема с временным объектом в цикле for на основе диапазона, этот ответ действительно помог мне понять.Но что мы можем сделать, чтобы предотвратить висячую ссылку?

Мое решение

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

РЕДАКТИРОВАТЬ: не использовать это решение,Это неверно, как указано в принятом решении.

template <typename T>
class reverse_range
{
  T &&rx_; // rvalue-reference to prolong livetime of temporary object
  T &x_; // reference to container

public:
  explicit reverse_range(T &x) : rx_(T{}), x_(x) {}
  explicit reverse_range(T &&rx) : rx_(std::move(rx)), x_(rx_) {}

  auto begin() const -> decltype(this->x_.rbegin())
  {
    return x_.rbegin();
  }  
  auto end() const -> decltype(this->x_.rend())
  {
    return x_.rend();
  }
};

template <typename T>
reverse_range<T> reverse_iterate(T &x)
{
  return reverse_range<T>(x);
}
template <typename T>
reverse_range<T> reverse_iterate(T &&rx)
{
  return reverse_range<T>(std::move(rx));
}

Очевидно, что мы генерируем небольшие накладные расходы на создание неиспользуемого пустого контейнерного объекта в конструкторе lvalue, но я думаю, что это не так уж плохо.Кроме того, можно, вероятно, избавиться от этого, предоставив два класса reverse_range_lvalue и reverse_range_rvalue, каждый из которых обеспечивает реализацию для одного из типов параметров ...

Вопросы

Будет ли указанное выше решение решить проблему с висящими ссылками или я что-то упущу?

Есть ли у вас какие-либо подсказки по дальнейшим проблемам, связанным с моим кодом?

Есть ли лучшие идеи для решения этой проблемыв C ++ 14 или любой другой (будущей) версии?

1 Ответ

0 голосов
/ 09 октября 2018

Это не работает.Расширение продолжительности жизни не работает в (этой части) конструкторов.(Он работает в теле конструктора, но не в списке инициализатора члена).

template<class R>
struct backwards_t {
  R r;
  constexpr auto begin() const { using std::rbegin; return rbegin(r); }
  constexpr auto begin() { using std::rbegin; return rbegin(r); }
  constexpr auto end() const { using std::rend; return rend(r); }
  constexpr auto end() { using std::rend; return rend(r); }
};
// Do NOT, I repeat do NOT change this to `backwards_t<std::decay_t<R>>.
// This code is using forwarding references in a clever way.
template<class R>
constexpr backwards_t<R> backwards( R&& r ) { return {std::forward<R>(r)}; }

это делает перемещение при передаче значения r и сохраняет ссылку при передаче значения lvalue.

Хитрость заключается в том, что для ссылки на пересылку T&&, если T&& является lvalue, тогда T является ссылкой, а если T&& является r-значением, то T является значением.Таким образом, мы конвертируем lvalues ​​в ссылки (и не копируем) при преобразовании rvalue в значения (и перемещаем rvalue в указанное значение).

for (const auto& v : backwards(getContainer()))

, чтобы это работало.

В Вы можете сделать немного "лучше";продление срока действия ссылки может применяться к содержимому структур, если вы выполняете агрегатную инициализацию.Но я бы посоветовал против этого;продление ссылочного срока службы хрупко и опасно при его разрыве.

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

Так что это известная проблема.Но это самый лучший и надежный способ решить эту проблему сегодня.

...