Как auto && продлит срок службы временного объекта?

Ниже приведен код ниже:

#include <iostream>


struct O
{
    ~O()
    {
        std::cout << "~O()\n";
    }
};

struct wrapper
{
    O const& val;

    ~wrapper()
    {
        std::cout << "~wrapper()\n";
    }
};

struct wrapperEx // with explicit ctor
{
    O const& val;

    explicit wrapperEx(O const& val)
      : val(val)
    {}

    ~wrapperEx()
    {
        std::cout << "~wrapperEx()\n";
    }
};

template<class T>
T&& f(T&& t)
{
    return std::forward<T>(t);
}


int main()
{
    std::cout << "case 1-----------\n";
    {
        auto&& a = wrapper{O()};
        std::cout << "end-scope\n";
    }
    std::cout << "case 2-----------\n";
    {
        auto a = wrapper{O()};
        std::cout << "end-scope\n";
    }
    std::cout << "case 3-----------\n";
    {
        auto&& a = wrapper{f(O())};
        std::cout << "end-scope\n";
    }
    std::cout << "case Ex-----------\n";
    {
        auto&& a = wrapperEx{O()};
        std::cout << "end-scope\n";
    }
    return 0;
}

Смотрите здесь live здесь.

Он сказал, что auto&& продлит срок службы временного объекта, но я не могу найти стандартные слова в этом правиле, по крайней мере, не в N3690.

Наиболее актуальным может быть раздел 12.2.5 о временном объекте, но не совсем то, что я ищу.

Итак, автоматически и правило продления срока службы применяется ко всем временным объектам, участвующим в выражении, или только к окончательному результату?

Более конкретно, a.val гарантированно является допустимым (не болтающимся) до того, как мы достигнем конца области в случае 1?

Edit: Я обновил пример, чтобы показать больше случаев (3 и Ex).

Вы увидите, что только в случае 1 продлевается время жизни O.

Ответ 1

Точно так же, как ссылка на const делает:

const auto& a = wrapper{O()};

или

const wrapper& a = wrapper{O()};

а также

wrapper&& a = wrapper{O()};

Более конкретно, a.val гарантированно действует (не болтается), прежде чем мы достигнем конца области в случае 1?

Да, это так.

Здесь (почти) ничего особенно важного в auto здесь. Это просто место для правильного типа (wrapper), которое выводится компилятором. Основной момент заключается в том, что временное связано с ссылкой.

Подробнее см. Кандидат на "Самый важный const" , который я цитирую:

Обычно временный объект длится только до конца полного выражения, в котором он появляется. Тем не менее, С++ преднамеренно указывает, что привязка временного объекта к ссылке на const в стеке увеличивает время жизни временного ресурса самой ссылки

Статья посвящена С++ 03, но аргумент остается в силе: временное может быть привязано к ссылке на const (но не на ссылку на const). В С++ 11 временная может также привязываться к ссылке rvalue. В обоих случаях время жизни временного объекта распространяется на время жизни ссылки.

Соответствующими частями стандарта С++ 11 являются те, которые указаны в OP, то есть 12.2 p4 и p5:

4 - Существует два контекста, в которых временные другой точки, чем конец полного выражения. Первый контекст является [...]

5 - Второй контекст - это когда ссылка привязана к временному. [...]

(Есть некоторые исключения в пунктах пули, следующих за этими строками.)

Обновить: (После комментария texasbruce.)

Причина, по которой O в случае 2 имеет короткий срок службы, состоит в том, что мы имеем auto a = wrapper{O()}; (см. здесь нет &), а затем временная не привязана к Справка. На самом деле временное копируется в a с использованием созданного компилятором конструктора-копии. Следовательно, временное не расширяет свой срок службы и умирает в конце полного выражения, в котором оно появляется.

В этом конкретном примере существует опасность, потому что wrapper::val является ссылкой. Созданный компилятором copy-constructor wrapper свяжет a.val с тем же объектом, к которому привязан временный член val. Этот объект также является временным, но имеет тип O. Затем, когда это последнее временное умирает, мы видим ~O() на экране и a.val болтается!

Контрастный случай 2 с этим:

std::cout << "case 3-----------\n";
{
    O o;
    auto a = wrapper{o};
    std::cout << "end-scope\n";
}

Выход (при компиляции с помощью gcc с использованием опции -fno-elide-constructors)

case 3-----------
~wrapper()
end-scope
~wrapper()
~O()

Теперь временная wrapper имеет член val, связанный с O. Обратите внимание, что O не является временным. Как я уже сказал, a является копией временного элемента wrapper, а a.val также привязывается к O. Перед тем, как заканчивается область действия, временные wrapper умирают, и мы видим первый ~wrapper() на экране.

Затем область заканчивается и мы получаем end-scope. Теперь a и O должны быть уничтожены в обратном порядке построения, поэтому мы видим ~wrapper(), когда a умирает и, наконец, ~O(), когда это время O. Это показывает, что a.val не болтается.

(Заключительное замечание: я использовал -fno-elide-constructors, чтобы предотвратить оптимизацию, связанную с копированием, которая затруднит обсуждение здесь, но это еще одна история ).