Почему ссылка на 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
.