Почему ссылка на const не продлевает жизнь временного объекта, переданного через функцию?

В следующем простом примере, почему ref2 не ref2 быть связан с результатом min(x,y+1)?

#include <cstdio>
template< typename T > const T& min(const T& a, const T& b){ return a < b ? a : b ; }

int main(){
      int x = 10, y = 2;
      const int& ref = min(x,y); //OK
      const int& ref2 = min(x,y+1); //NOT OK, WHY?
      return ref2; // Compiles to return 0
}

живой пример - выдает:

main:
  xor eax, eax
  ret

РЕДАКТИРОВАТЬ: Ниже пример лучше описал ситуацию, я думаю.

#include <stdio.h>


template< typename T >
constexpr T const& min( T const& a, T const& b ) { return a < b ? a : b ; }



constexpr int x = 10;
constexpr int y = 2;

constexpr int const& ref = min(x,y);  // OK

constexpr int const& ref2 = min(x,y+1); // Compiler Error

int main()
{
      return 0;
}

живой пример выдает:

<source>:14:38: error: '<anonymous>' is not a constant expression

 constexpr int const& ref2 = min(x,y+1);

                                      ^

Compiler returned: 1

Ответы

Ответ 1

Это по замыслу. В двух словах, только именованная ссылка, к которой привязан временный файл, продлит срок его службы.

[class.temporary]

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

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

  • Временный объект, связанный со ссылочным параметром в вызове функции, сохраняется до завершения полного выражения, содержащего вызов.
  • Время жизни временной привязки к возвращаемому значению в операторе возврата функции не продлевается; временное уничтожается в конце полного выражения в операторе возврата.
  • [...]

Вы не связывались напрямую с ref2, и вы даже ref2 его через оператор return. Стандарт прямо говорит, что он не продлит срок службы. Частично, чтобы сделать возможной определенную оптимизацию. Но, в конечном счете, потому что отслеживание того, какой временный объект должен быть расширен, когда ссылка передается в и из функций, вообще трудно поддается решению.

Поскольку компиляторы могут активно оптимизировать, предполагая, что ваша программа не имеет неопределенного поведения, вы видите возможное проявление этого. Доступ к значению вне его времени жизни не определен, вот что return ref2; делает, и так как поведение не определено, просто возвращение нуля является допустимым поведением для демонстрации. Ни один контракт не нарушается компилятором.

Ответ 2

Это намеренно. Ссылка может продлить срок действия временного объекта, только если он напрямую связан с этим временным объектом. В вашем коде вы привязываете ref2 к результату min, который является ссылкой. Неважно, что эта ссылка ссылается на временную. Только b продлевает время жизни временного; Неважно, что ref2 также относится к тому же временному.

Еще один способ взглянуть на это: у вас не может быть дополнительного продления жизни. Это статическое свойство. Если ref2 будет делать правильные вещи тм, а затем в зависимости от значений во время выполнения x и y+1 срок службы продлевается или нет. Не то, что компилятор может сделать.

Ответ 3

Сначала я отвечу на вопрос, а затем предоставлю некоторый контекст для ответа. Текущий рабочий проект содержит следующую формулировку:

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

  • временное преобразование материализации ([conv.rval]),
  • ( выражение ), где выражение является одним из этих выражений,
  • подписка ([expr.sub]) операнда массива, где этот операнд является одним из этих выражений,
  • доступ к членам класса ([expr.ref]) с использованием . оператор, где левый операнд является одним из этих выражений, а правый операнд обозначает нестатический элемент данных не ссылочного типа,
  • операция указателя на член ([expr.mptr.oper]) с использованием оператора .* где левый операнд является одним из этих выражений, а правый операнд является указателем на член данных не ссылочного типа,
  • const_cast ([expr.const.cast]), static_cast ([expr.static.cast]), dynamic_cast ([expr.dynamic.cast]) или reinterpret_cast ([expr.reinterpret.cast]) без пользователя -определенное преобразование, операнд glvalue, который является одним из этих выражений, в glvalue, который относится к объекту, указанному операндом, или к его полному объекту или подобъекту,
  • условное выражение ([expr.cond]), которое является glvalue, где второй или третий операнд является одним из этих выражений, или
  • выражение запятой ([expr.comma]), которое является glvalue, где правый операнд является одним из этих выражений.

В соответствии с этим, когда ссылка связывается с glvalue, возвращаемым из вызова функции, расширение времени жизни не происходит, потому что glvalue было получено из вызова функции, который не является одним из разрешенных выражений для расширения времени жизни.

Время жизни y+1 временного продлевается один раз, когда связанный с опорным параметром b. Здесь значение y+1 материализуется, чтобы получить значение x, и ссылка привязывается к результату преобразования временной материализации; таким образом происходит продление жизни. Однако, когда функция min возвращается, ref2 привязывается к результату вызова, и продление срока службы здесь не происходит. Следовательно, временное значение y+1 уничтожается в конце определения ref2, а ref2 становится висячей ссылкой.


Исторически возникла путаница в этой теме. Хорошо известно, что код OP и подобный код приводят к висячей ссылке, но стандартный текст, даже на С++ 17, не дал однозначного объяснения, почему.

Часто утверждается, что продление срока службы применяется только тогда, когда ссылка привязывается "напрямую" к временному, но стандарт никогда ничего не говорил об этом. Действительно, стандарт определяет, что он означает для ссылки "связать напрямую", и это определение (например, const std::string& s = "foo"; является косвенной привязкой ссылки) здесь явно не имеет значения.

Rakete1111 сказал в комментарии в другом месте SO, что продление времени жизни применяется только тогда, когда ссылка привязывается к prvalue (а не к некоторому glvalue, который был получен посредством предыдущей привязки ссылки к этому временному объекту); Похоже, они говорят что-то похожее здесь "связанными... напрямую". Тем не менее, нет текстовой поддержки этой теории. Действительно, код, подобный следующему, иногда рассматривается для запуска продления жизни:

struct S { int x; };
const int& r = S{42}.x;

Однако в С++ 14 выражение S{42}.x стало значением x, поэтому если здесь применяется продление времени жизни, то это не так, поскольку ссылка привязывается к prvalue.

Вместо этого можно утверждать, что продление срока действия применяется только один раз, и привязка любых других ссылок к тому же объекту не продлевает его срок службы. Это объясняет, почему код OP создает висячую ссылку, не предотвращая продление срока службы в случае S{42}.x. Тем не менее, в стандарте также нет заявления об этом.

StoryTeller также сказал здесь, что ссылка должна быть связана напрямую, но я тоже не знаю, что он имеет в виду. Он цитирует стандартный текст, указывающий, что привязка ссылки к временному в операторе return не продлевает срок его службы. Однако это утверждение, похоже, предназначено для применения к случаю, когда рассматриваемый временный объект создается полным выражением в операторе return, поскольку в нем говорится, что временный объект будет уничтожен в конце этого полного выражения. Очевидно, что это не так для временного значения y+1, которое вместо этого будет уничтожено в конце полного выражения, содержащего вызов min. Таким образом, я склонен думать, что это утверждение не было предназначено для применения в подобных случаях в этом вопросе. Вместо этого его эффект, наряду с другими ограничениями на продление срока службы, состоит в том, чтобы предотвратить продление срока действия любого временного объекта за пределы области блока, в которой он был создан. Но это не помешает сохранению временного значения y+1 в вопросе до конца main.

Таким образом, остается вопрос: каков принцип, объясняющий, почему привязка ref2 к временному в вопросе не продлевает этот временный срок службы?

Формулировка текущего рабочего проекта, которую я цитировал ранее, была введена резолюцией CWG 1299, которая была открыта в 2011 году, но была решена только недавно (не вовремя для С++ 17). В некотором смысле это проясняет интуицию о том, что ссылка должна связываться "напрямую", определяя те случаи, когда привязка является "прямой", достаточной для продления срока службы; однако, оно не настолько ограничительно, чтобы разрешать его только тогда, когда ссылка привязывается к prvalue. Это позволяет продлить время жизни в случае S{42}.x.