Почему в следующем простом примере 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
Какую ошибку выдает?
На самом деле это очень интересный вопрос. Я назову юриста по языку и надеюсь, что кто-нибудь из начальников возьмется за это дело. У меня нет ни времени, ни опыта. Все дело в том, что продление жизни не является транзитивным, и в том, где находятся исходные объекты. Продолжайте @StoryTeller.
Кстати, предпочитаю return b < a ? b : a; за реализацию.
@Bathsheba Не могли бы вы быстро объяснить, почему b < a ? b : a это выгодно?
Может это у меня незнание С++, но только в этой ситуации смысла нет? Функция возвращает временный объект, который больше нигде не живет. Как ссылка может относиться к чему-то, что больше не живет?
@Michiel Дело в том, что если бы привязка была прямой (например, если бы min возвращалась по значению), ref2 продлила бы время жизни привязанного временного объекта до самого себя. Отсюда и вопрос, наверное.
@lubgr: все дело в соглашении о возврате определенного параметра в случае ничьей или NaN: например. с плавающей запятой со знаком нуля.
@lubgr: Связано: Зачем использовать «b < a ? a : b» вместо «a < b? b : a», чтобы реализовать максимальный шаблон?
@ВТТ; Я запутался. Программа делает выдает результат — значение «ref2», которое номинально равно min(10,3). Хотя я думаю, что вижу проблему. Поскольку это ссылки, "y+1" привел к временной переменной для хранения значения, ссылка на которую была передана в функцию min(), которая вернула временное значение, которое затем выпало из области видимости, оставив ссылку ref2 в недопустимом местоположении. . У меня все правильно?
@EdwardFalk С тех пор вопрос был отредактирован. Первый вариант не использовал ref2, а часть NOT OK была на самом деле полностью в порядке (за исключением того, что она не была привязана к временной, что было совершенно неуместно).
Были предложения (отклонены?) ввести маркер, явно указывающий, что функция может возвращать ссылку на конкретный аргумент, и что в любом месте, где могло произойти продление жизни для возвращаемого значения, это должно произойти для аргумента. В этом случае, если min был отмечен на обоих аргументах, это означает, что const auto&z=min(x+1,y+1) продлит время жизни как x+1, так и y+1.





Это сделано намеренно. Ссылка может продлить время жизни временного объекта только в том случае, если он привязан к этому временному напрямую. В вашем коде вы привязываете ref2 к результату min, который является ссылкой. Неважно, что эта ссылка ссылается на временный объект. Только b продлевает срок службы временного; не имеет значения, что ref2 также относится к тому же временному.
Другой способ взглянуть на это: у вас не может быть дополнительного продления жизни. Это статическое свойство. Если ref2 поступил бы правильноtm, то в зависимости от значений времени выполнения x и y+1 срок службы продлевается или нет. Не то, что компилятор может сделать.
Это по дизайну. В двух словах, только ссылка названный, к которой привязан временный объект напрямую, продлевает его время жизни.
[class.temporary]
5 There are three contexts in which temporaries are destroyed at a different point than the end of the full-expression. [...]
6 The third context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:
- A temporary object bound to a reference parameter in a function call persists until the completion of the full-expression containing the call.
- The lifetime of a temporary bound to the returned value in a function return statement is not extended; the temporary is destroyed at the end of the full-expression in the return statement.
- [...]
Вы не привязывались напрямую к ref2 и даже передавали его через оператор return. Стандарт прямо говорит, что это не продлит срок службы. Отчасти для того, чтобы сделать возможными определенные оптимизации. Но в конечном счете, потому что отслеживание того, какое временное значение должно быть расширено, когда ссылка передается в функции и из них, в общем случае неразрешимо.
Поскольку компиляторы могут проводить агрессивную оптимизацию, предполагая, что ваша программа не проявляет неопределенного поведения, вы видите возможное проявление этого. Доступ к значению за пределами его времени жизни не определен, это то, что return ref2;делает, и, поскольку поведение не определено, просто возврат нуля является допустимым поведением для демонстрации. Ни один контракт не нарушается компилятором.
Большое спасибо. Я ошибочно полагал, что y+1 временный объект привязывается к b и расширяется через функцию возврата. .
Сначала я отвечу на вопрос, а затем предоставлю некоторый контекст для ответа. Текущий рабочий проект содержит следующую формулировку:
The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:
- a temporary materialization conversion ([conv.rval]),
(expression), where expression is one of these expressions,- subscripting ([expr.sub]) of an array operand, where that operand is one of these expressions,
- a class member access ([expr.ref]) using the
.operator where the left operand is one of these expressions and the right operand designates a non-static data member of non-reference type,- a pointer-to-member operation ([expr.mptr.oper]) using the
.*operator where the left operand is one of these expressions and the right operand is a pointer to data member of non-reference type,- a
const_cast([expr.const.cast]),static_cast([expr.static.cast]),dynamic_cast([expr.dynamic.cast]), orreinterpret_cast([expr.reinterpret.cast]) converting, without a user-defined conversion, a glvalue operand that is one of these expressions to a glvalue that refers to the object designated by the operand, or to its complete object or a subobject thereof,- a conditional expression ([expr.cond]) that is a glvalue where the second or third operand is one of these expressions, or
- a comma expression ([expr.comma]) that is a glvalue where the right operand is one of these expressions.
В соответствии с этим, когда ссылка привязана к значению gl, возвращенному из вызова функции, продление срока службы не происходит, поскольку значение gl было получено из вызова функции, что не является одним из допустимых выражений для продления времени жизни.
Время жизни временного объекта y+1 продлевается один раз при привязке к ссылочному параметру b. Здесь значение prvalue y+1 материализуется для получения значения x, а ссылка привязывается к результату преобразования временной материализации; Таким образом, происходит продление срока службы. Однако когда функция min возвращает значение, ref2 привязывается к результату вызова, и здесь не происходит продления срока службы. Следовательно, временное y+1 уничтожается в конце определения ref2, а ref2 становится висячей ссылкой.
Исторически сложилась некоторая путаница в этой теме. Хорошо известно, что код OP и аналогичный код приводят к оборванной ссылке, но стандартный текст, даже начиная с C++ 17, не дает однозначного объяснения, почему.
Часто утверждается, что продление срока службы применяется только тогда, когда ссылка «напрямую» связывается с временным, но стандарт никогда ничего не говорил об этом. Действительно, стандарт определяет, что значит для ссылки «привязать напрямую», и это определение (например, const std::string& s = "foo"; — косвенная привязка ссылки) здесь явно не уместно.
Rakete1111 сказал в другом комментарии к SO, что продление срока службы применяется только тогда, когда ссылка привязывается к значению prvalue (а не к некоторому значению gl, которое было получено посредством предыдущей привязки ссылки к этому временному объекту); они, кажется, говорят что-то подобное здесь, «связанные ... напрямую». Однако текстовой поддержки этой теории нет. Действительно, иногда считалось, что код, подобный приведенному ниже, запускает продление срока службы:
struct S { int x; };
const int& r = S{42}.x;
Однако в C++14 выражение S{42}.x стало значением x, поэтому если здесь применяется продление срока службы, то это происходит не потому, что ссылка привязывается к значению prvalue.
Вместо этого можно заявить, что продление времени жизни применяется только один раз, и привязка любых других ссылок к тому же объекту не увеличивает его время жизни. Это объясняет, почему код OP создает оборванную ссылку, не предотвращая продление срока службы в случае S{42}.x. Однако и в стандарте нет никаких указаний на этот счет.
StoryTeller также сказал здесь, что ссылка должна быть привязана напрямую, но я тоже не знаю, что он имеет в виду. Он цитирует текст стандартов, указывающий, что привязка ссылки к временному объекту в операторе return не продлевает срок его службы. Однако это утверждение, по-видимому, предназначено для случая, когда рассматриваемое временное создается полным выражением в выражении return, поскольку оно говорит, что временное будет уничтожено в конце этого полного выражения. Очевидно, что это не относится к временному y+1, которое вместо этого будет уничтожено в конце полного выражения, содержащего вызов min. Таким образом, я склонен думать, что это утверждение не предназначалось для применения к случаям, подобным рассматриваемому в вопросе. Вместо этого его эффект вместе с другими ограничениями на продление срока службы составляет предотвратить расширение срока службы любого временного объекта за пределы области блока, в которой он был создан. Но это не помешало бы временному y+1 в вопросе дожить до конца main.
Таким образом, остается вопрос: каков принцип, объясняющий, почему привязка ref2 к временному объекту в вопросе не продлевает срок службы этого временного объекта?
Формулировка текущего рабочего проекта, которую я цитировал ранее, была введена резолюцией CWG 1299, которая была открыта в 2011 году, но разрешена только недавно (не вовремя для C++17). В некотором смысле, это проясняет интуицию о том, что ссылка должна связываться «напрямую», очерчивая те случаи, когда связывание является достаточно «прямым», чтобы произошло продление срока службы; однако он не настолько ограничителен, чтобы разрешать его только тогда, когда ссылка привязывается к значению prvalue. Это позволяет продлить срок службы в случае S{42}.x.
[Ответ должен быть обновлен, поскольку версия, отличная от constexpr, фактически компилируется]
Версия без constexpr
Демо:https://godbolt.org/z/_p3njK
Объяснение: Срок службы рассматриваемого значение, произведенного y + 1, фактически продлен. Это связано с тем, что return тип min является ссылкой на константу, то есть const T&, и всякий раз, когда у вас есть ссылка на константу связывание напрямую для типа значение, время жизни базового значения rvalue продлевается до тех пор, пока не будет существовать ссылка на константу.
Идя дальше, вывод ссылки на константу min затем присваивается непосредственно имени lvalue ref2, тип которого const int&; здесь тоже должен работать тип int (то есть int ref2 = min(x, y+1);), и в этом случае базовый значение будет скопирован, а ссылка на константу будет уничтожена.
Таким образом, версия, отличная от constexpr, всегда должна давать желаемый результат, по крайней мере, в последней версии компиляторов, соответствующих современному стандарту C++.
constexpr версия
Проблема здесь в другом, поскольку спецификатор типа ref2 требует, чтобы он был constexpr, что, в свою очередь, требует, чтобы выражение было литералом времени компиляции. Хотя теоретически продление времени жизни может быть применено здесь для constexpr типов ссылки на константу, C++ пока не разрешает этого (т. е. он не создает временные constexpr типы для хранения базового значения), возможно, потому, что он запрещает некоторые оптимизации или усложняет работу компилятора - не уверен, какой именно.
Тем не менее, вы должны быть в состоянии обойти этот случай тривиально:
constexpr int value = min(x, y + 1);
constexpr int const& ref2 = value;
Эта программа не производит вывода и завершается с кодом 0. С флагом оптимизации
-O3все операторы внутри main отбрасываются.