Избегайте обвинчивания ссылки для обратной линии на основе цикла
Предыстория и предыдущий поиск
Я ищу элегантный способ обратной обработки по контейнеру (например, 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()))
так что работает.
В c++17 вы можете сделать немного "лучше"; эталонное продление жизненного цикла может применяться к содержимому структур, если вы выполняете агрегационную инициализацию. Но я бы посоветовал это сделать; расширение ссылки срок службы является хрупким и опасным, когда она ломается.
Существует беседа в c++20 или позже, чтобы позволить компиляторам преобразовывать перемещения в объекты с истечением срока действия в эльфы. Но я бы не стал делать ставку на то, что он работает в конкретном случае. Я также думаю, что я видел документ о разметке ctors и функциях с их информацией о зависимости жизни (то есть, что возвращаемое значение зависит от времени жизни аргумента), разрешая предупреждения/ошибки и, возможно, более обобщенное продление жизни.
Так что это известная проблема. Но это лучший в целом безопасный способ решить эту проблему сегодня.