О привязке ссылки const к под-объекту временного

С кодом типа

#include <iostream>

struct P {
    int x;
    P(int x) : x(x) {}
    ~P() { std::cout << "~P()\n"; }
};

int main() {
    auto const& x = P{10}.x;
    std::cout << "extract\n";
}

GCC печатает ~P() extract, указывая, что временное время жизни не увеличивается по ссылке.

В отличие от этого, Clang (IMO правильно) продлевает время жизни временного ресурса ссылки x, и поэтому деструктор будет вызываться после вывода в main.

Обратите внимание, что GCC неожиданно показывает поведение Клана, если вместо int использовать некоторый тип класса (например, string).

Является ли это ошибкой в ​​GCC или что-то, что разрешено стандартом?

Ответ 1

Это распространяется на CWG 1651:

Разрешение проблем 616 и 1213, в результате чего участник выражение доступа или подстроки, примененное к значению prvalue, значение x, означает что привязка ссылки на такой подобъект временного продлить временное время жизни. 12.2 [class.temporary] должен быть пересмотренный, чтобы убедиться в этом.

Статус-кво состоит в том, что только prvalues ​​обрабатываются как ссылающиеся на временные файлы - таким образом [class.temporary]/5 ( "Второй контекст - это когда ссылка привязана к временному." ) не считается применимым. Тем не менее, Clang и GCC фактически не реализовали разрешение 616. center().x рассматривается как prvalue как. Мое лучшее предположение:

  • GCC просто никак не реагировал на какие-либо DR. Он не продлевает время жизни при использовании скалярных подобъектов, поскольку они не покрыты [dcl. init.ref]/(5.2.1.1) . Таким образом, полный временный объект не должен жить (см. ответ aschelper), и это не так, потому что ссылка не связывается напрямую. Если подобъект имеет тип класса или массива, ссылка привязывается напрямую, а GCC расширяет временное время жизни. Это было отмечено в DR 60297.

  • Clang распознает доступ к члену и уже реализовал "новые" правила продления жизни - он даже обрабатывает приведения. С технической точки зрения это не согласуется с тем, как он обрабатывает категории значений. Тем не менее, это более разумно и будет правильным поведением после устранения вышеупомянутого DR.

Поэтому я бы сказал, что GCC корректен в соответствии с текущей формулировкой, но текущая формулировка является дефектной и неопределенной, и Кланг уже выполнил ожидающее решение DR 1651, которое N3918. Эта статья очень четко описывает пример:

Если E1 - временное выражение, а E2 не обозначает бит-поле, то E1.E2 является временным выражением.

center() является временным выражением в соответствии с бумажной формулировкой для [expr.call]/11. Таким образом, его модифицированная формулировка в вышеупомянутом [class.temporary]/5 применяется:

Второй контекст - это когда ссылка не связывается напрямую (8.5.3 dcl.init.ref) или инициализируется временным выражением (раздел 5). Соответствующий временный объект (если таковой имеется) сохраняется для ресурса ссылки, кроме: [... неприменимых исключений...]

Voilà, у нас есть продление жизни. Обратите внимание, что "соответствующий временный объект" недостаточно ясен, одна из причин отсрочки предложения; он, несомненно, будет принят после его пересмотра.


- это xvalue (но не бит-поле), класс prvalue, array prvalue или функция lvalue и "cv1 T1" ссылаются на "cv2 T2", или [... ]

Действительно, GCC полностью уважает это и продлит время жизни, если подобъект имеет тип массива.

Ответ 2

Я бы поспорил с ошибкой в ​​g++, потому что, цитируя черновик N3242, §12.2/5:

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

Таким образом, его время жизни должно быть расширено, за исключением случаев, когда:

Временная привязка к ссылочному элементу в конструкторе ctor-initializer [..]

Временная привязка к эталонному параметру в вызове функции [..]

Время жизни временной привязки к возвращаемому значению в функции return return [..]

Временная привязка к ссылке в new-initializer [..]

Наш случай не соответствует ни одному из этих исключений, поэтому он должен следовать правилу. Я бы сказал, что g++ здесь не так.

Затем, в отношении цитаты aschepler, поднятой из того же проекта §8.5.3/5 (акцент мой):

Ссылка на тип "cv1 T1" инициализируется выражением типа "cv2 T2" следующим образом:

  • Если ссылка является ссылкой lvalue и выражением инициализатора

    а. является lvalue (но не является битовым полем), а "cv1 T1" ссылается на "cv2 T2" или

    б. имеет тип класса...

    затем...

  • В противном случае ссылка должна быть ссылкой lvalue на нелетучий const-тип (т.е. cv1 должен быть const), или ссылка должна быть ссылкой rvalue.

    а. Если выражение инициализатор

    • я. - это значение xvalue, класс prvalue, значение prvalue массива или функция lvalue и "cv1 T1" ссылаются на "cv2 T2" или

    • II. имеет тип класса...

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

    б. В противном случае временный тип "cv1 T1" создается и инициализируется из выражения инициализатора, используя правила для неосновной копии-инициализации (8.5). Ссылка затем привязана к временному.

Глядя на то, что такое xvalue, на этот раз цитируя http://en.cppreference.com/w/cpp/language/value_category...

Выражение xvalue ( "expired value" ) является [..]

a.m, член выражения объекта, где a - значение r, а m - нестатический элемент данных не ссылочного типа;

... выражение center().x должно быть значением x, поэтому применяется случай 2a из §8.5.3/5 (и не). Я останусь с моим предложением: g++ ошибается.

Ответ 3

Просто прочитайте ответ Колумба.


Это ошибка gcc. Соответствующее правило находится в [class.temporary]:

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

Второй контекст - это когда привязка привязана к временному. Временная привязка ссылки или временный объект , являющийся полным объектом подобъекта, к которому привязана ссылка, сохраняется для времени жизни ссылки, за исключением:
- Временный объект, привязанный к эталонному параметру в вызове функции (5.2.2), сохраняется до завершения полного выражения, содержащего вызов.
- Время жизни временной привязки к возвращаемому значению в операторе return функции (6.6.3) не является продлен; временное уничтожается в конце полного выражения в операторе return.
- Временная привязка к ссылке в новом инициализаторе (5.3.4) сохраняется до завершения полное выражение, содержащее новый инициализатор.

Мы привязываем ссылку на подобъект временного, поэтому временное должно сохраняться для срока службы ссылки. Ни одно из этих трех исключений из этого правила не применяется здесь.

Ответ 4

EDIT: этот ответ неверен! Как указывали другие, center().x - это значение x, а не rvalue.

g++ верен, и это ошибка в clang. Из черновика N3242, раздел 8.5.3/5, с точками пули, измененными на числа:

Ссылка на тип "cv1 T1" инициализируется выражением типа "cv2 T2" следующим образом:

  • Если ссылка является ссылкой lvalue и выражением инициализатора

    а. является lvalue (но не является битовым полем), а "cv1 T1" ссылается на "cv2 T2" или

    б. имеет тип класса...

    затем...

  • В противном случае ссылка должна быть ссылкой lvalue на нелетучий const-тип (т.е. cv1 должен быть const), или ссылка должна быть ссылкой rvalue.

    а. Если выражение инициализатора

    • я. это значение xvalue, класс prvalue, значение prvalue массива или функция lvalue, а "cv1 T1" ссылается на "cv2 T2" или

    • II. имеет тип класса...

    тогда ссылка привязана к значению выражения инициализатора в первом случае....

    б. В противном случае временный тип "cv1 T1" создается и инициализируется из выражения инициализатора, используя правила для неосновной копии-инициализации (8.5). Ссылка затем привязана к временному.

Так как выражение инициализатора является неклассовым скалярным значением, то точка 2.a.i. не применяется, и мы закончим в случае 2.b. Временный double должен быть создан путем инициализации копирования, а ссылка привязывается к этому, а не к подобъекту center().

Как вы заметили, если выражение имеет тип класса, все по-другому, и ссылка напрямую связывается с подобъектом.