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

В следующем простом примере, почему 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

EDIT: Ниже пример лучше описал ситуацию, я думаю.

#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

Ответы [ 3 ]

38 голосов
/ 08 апреля 2019

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

[class.teven]

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

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

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

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

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

16 голосов
/ 08 апреля 2019

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

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

8 голосов
/ 08 апреля 2019

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

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

  • временное преобразование материализации ([conv.rval]),
  • ( выражение ), где выражение является одним из этих выражений,
  • подписка ([expr.sub]) операнда массива, где этот операнд является одним из этихвыражения,
  • доступ к члену класса ([expr.ref]) с использованием оператора ., где левый операнд является одним из этих выражений, а правый операнд обозначает нестатический элемент данных не ссылочного типа,
  • операция указателя на член ([expr.mptr.oper]) с использованием оператора .*, где левый операнд является одним из этих выражений, аправый операнд - указатель на элемент данных не ссылочного типа,
  • a 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 и подобный код приводят к висячей ссылке, но стандартный текст, даже на C ++ 17, не дает однозначного объяснения, почему.

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

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

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...