Избегайте обвинчивания ссылки для обратной линии на основе цикла

Предыстория и предыдущий поиск

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

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

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

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

Мое решение

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

EDIT: Не используйте это решение. Это неправильно, как указано в принятом решении.

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, каждый из которых обеспечивает реализацию для одного из типов параметров...

Вопросы

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

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

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

Ответ 1

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

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)}; }

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

Фокус в том, что для справочника T&& пересылки, если T&& является lvalue, тогда T является ссылкой, и если T&& является rvalue, тогда T является значением. Поэтому мы преобразуем lvalues в ссылки (и не делаем копии) при преобразовании значений r в значения (и перемещаем rvalue в указанное значение).

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

так что работает.

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

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

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