Каково время жизни аргумента по умолчанию, связанного с ссылочным параметром?

Я думал, что ссылки только продлевают время жизни временного ресурса на время жизни самой ссылки, но вывод следующего фрагмента кажется противоречивым:

#include <iostream>

struct X{ ~X(){ std::cout << "Goodbye, cruel world!\n"; } };

X const& f(X const& x = X()){
  std::cout << "Inside f()\n";
  return x;
}

void g(X const& x){
  std::cout << "Inside g()\n";
}

int main(){
  g(f());
}

Пример в реальном времени. Выход:

Inside f()
Inside g()
Goodbye, cruel world!

Итак, кажется, что временное уничтожается после вызова g()... что дает?

Ответ 1

Стандарт обрабатывает это в специальном случае в §12.2 [class.temporary]:

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

p5 Второй контекст - это когда ссылка привязана к временному. Временное, к которому привязана ссылка, или временное, являющееся полным объектом подобъекта, к которому привязана ссылка, сохраняется для времени жизни ссылки, за исключением:

  • Временная привязка к ссылочному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов.

Стандарт также имеет удобную заметку о полных выражениях и оценку их подвыражений в отношении параметров по умолчанию в §1.9 [intro.execution] p11:

[Примечание. Оценка полного выражения может включать в себя оценку подвыражений, которые не являются лексически частью полного выражения. Например, подвыражения, участвующие в оценке аргументов по умолчанию (8.3.6), считаются создаваемыми в выражении, которое вызывает функцию, а не выражение, которое определяет аргумент по умолчанию. -end note]

Ответ 2

Интересно, +1. (Я не хочу конкурировать с вашим хорошим ответом на себя здесь). Просто примечание для всех, кого это интересует. Если вам нужен аналогичный эффект, но позволяющий неконстантировать, вы можете использовать семантику перемещения:

#include <iostream>

struct X{
   ~X(){ std::cout << "Goodbye, cruel world!\n"; }
   X(X && x){  std::cout << "moved "; }
   X(){}
};

X  f(X x = X()){
  std::cout << "Inside f()\n";
  return x;
}

void g(X x){
  std::cout << "Inside g()\n";
}

int main(){
   g(f());
}

дает

Inside f()
moved Inside g()
Goodbye, cruel world!
Goodbye, cruel world!