Ответ 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
, чтобы предотвратить оптимизацию, связанную с копированием, которая затруднит обсуждение здесь, но это еще одна история ).