Подтвердить что ты не робот

Временное время жизни в диапазоне - для выражения

Рассмотрим простой класс A, который может использоваться как диапазон:

struct A { 
    ~A() { std::cout << "~A "; }

    const char* begin() const {
        std::cout << "A::begin ";
        return s.data();
    }   

    const char* end() const {
        std::cout << "A::end ";
        return s.data() + s.size();
    }   

    std::string s;
};

Если я делаю временный A в диапазоне, он работает точно так, как я надеюсь:

for (auto c : A{"works"}) {
    std::cout << c << ' ';
} 

// output
A::begin A::end w o r k s ~A 

Однако, если я попытаюсь обернуть временное:

struct wrap {
    wrap(A&& a) : a(std::move(a))
    { } 

    const char* begin() const { return a.begin(); }
    const char* end() const { return a.end(); }

    A&& a;
};

for (auto c : wrap(A{"fails"})) {
    std::cout << c << ' ';
}

// The temporary A gets destroyed before the loop even begins: 
~A A::begin A::end 
^^

Почему A срок жизни не расширен для полного выражения для диапазона, и как я могу это сделать, не прибегая к созданию копии A?

4b9b3361

Ответ 1

Расширение Lifetime появляется только при привязке непосредственно к ссылкам вне конструктора.

Расширение жизненного цикла ссылки внутри конструктора будет технически сложным для компиляторов для реализации.

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

struct wrap {
  wrap(A&& a) : a(std::move(a))
  {} 

  const char* begin() const { return a.begin(); }
  const char* end() const { return a.end(); }

  A a;
};

Во многих контекстах wrap сам является шаблоном:

template<class A>
struct wrap {
  wrap(A&& a) : a(std::forward<A>(a))
  {} 

  const char* begin() const { return a.begin(); }
  const char* end() const { return a.end(); }

  A a;
};

и если A является Foo& или Foo const&, ссылки сохраняются. Если это Foo, тогда будет сделана копия.

Примером такого шаблона в использовании будет if wrap, где называется backwards, и он возвращает итераторы, где обратные итераторы построены из A. Затем временные диапазоны будут скопированы в backwards, а не временные объекты будут просто просмотрены.

В теории язык, позволяющий вам разметки параметров функциям и конструкторам, является "зависимыми источниками", срок жизни которых должен быть расширен до тех пор, пока интересное значение объекта/возврата будет интересным. Это, наверное, сложно. Например, представьте new wrap( A{"works"} ) - время автоматического хранения временного хранения должно продолжаться до тех пор, пока свободное хранилище wrap!

Ответ 2

Причина, по которой время жизни не продлевается, заключается в том, как стандарт определяет диапазон для циклов в

6.5.4 Операция на основе диапазона [stmt.ranged]

1 Для оператора for на основе диапазона формы

for ( для диапазона декларирование : выражение ) выражение

пусть range-init будет эквивалентен выражению, окруженному круглыми скобками

( expression )

и для оператора for на основе диапазона формы

for ( для диапазона декларирование : рамно-Init-лист ) Заявление

пусть range-init будет эквивалентен списку с привязкой к init-init. В каждом случае оператор for на основе диапазона эквивалентен

{
   auto && __range = range-init;
   for ( auto __begin = begin-expr,
              __end = end-expr;
         __begin != __end;
         ++__begin ) {
      for-range-declaration = *__begin;
      statement
   }
}

Обратите внимание, что auto && __range = range-init; расширяет время жизни временного объекта, возвращаемого из диапазона-init, но не продлевает время жизни вложенных временных рядов внутри диапазона-init.

Это ИМХО очень неудачное определение и даже обсуждалось как Отчет о дефектах 900. Кажется, что это единственная часть стандарта, где ссылка неявно связана с расширением времени жизни результата выражения без увеличения времени жизни вложенных временных рядов.

Решение состоит в том, чтобы сохранить копию в обертке, которая часто поражает цель обертки.