Продление срока службы временного по неконстантной ссылке с использованием const-cast

Это то, что возникло недавно и, как мне кажется, не должно работать так, как, по-видимому, работает:

#include <iostream>
#include <memory>

int main()
{
    std::shared_ptr<int>& ptr = const_cast<std::shared_ptr<int>&>(
        static_cast<const std::shared_ptr<int>&>(
            std::shared_ptr<int>(
                new int(5), [](int* p) {std::cout << "Deleting!"; *p = 999;  delete(p); }
            )
        )
    );
    std::cout << "I'm using a non-const ref to a temp! " << *ptr << " ";
}

Использование shared_ptr здесь не обязательно, но настраиваемое средство удаления позволяет легко продемонстрировать время жизни полученного объекта. Итоговый результат Visual Studio, Clang и GCC одинаков:

Я использую неконстантную ссылку на темп! 5 Удаление!

Это означает, что время жизни результирующего shared_ptr с помощью некоторого механизма было увеличено до соответствия сроку жизни std::shared_ptr<int>& ptr.

Что творится?

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

Кроме того, у Microsoft есть расширение, которое позволяет неконстантным ссылкам продлевать время существования привязанного временного объекта, но такое поведение, по-видимому, присутствует, даже когда это расширение отключено, и, кроме того, также появляется в Clang и GCC.

Согласно этому ответу, я считаю, что временный объект неявно создается как const, поэтому попытка изменить объект, на который ссылается ptr, вероятно, undefined, но я не уверен, что эти знания что-то говорят мне о том, почему продлевается время жизни. Насколько я понимаю, это акт изменения константы, которая является UB, а не просто взятие на нее неконстантной ссылки.

Я понимаю, что должно происходить следующим образом:

  1. Type() создает prvalue без спецификации cv.

  2. static_cast<const Type&>(...) материализует это prvalue в const xvalue со временем жизни, равным внутреннему выражению. Затем мы создаем ссылку const lvalue на это const xvalue. Время жизни xvalue увеличивается, чтобы соответствовать времени жизни ссылки const lvalue.

  3. const_cast<Type&>(...) создает ссылку lvalue, которая затем присваивается ptr. Срок действия ссылки на const lvalue истекает, а вместе с ним и материализованное значение x.

  4. Я пытаюсь прочитать висящую ссылку ptr, и случаются неприятности.

Что не так в моем понимании? Почему не выделены курсивом фрагменты?

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


person Steelbadger    schedule 04.12.2019    source источник
comment
если вы правильно поняли, то есть UB, а вывод - всего лишь симптом UB. Из всех плохих вещей худшее, похоже, работает так, как ожидалось. Во всяком случае, я надеюсь, что может быть ответ, который не нуждается в x / p / r-mumbo-jumbo;)   -  person 463035818_is_not_a_number    schedule 04.12.2019
comment
@ previouslyknownas_463035818: Я запускал код с помощью UB Sanitizer (также Address Sanitizer), и он не жаловался. Это не значит, что UB нет, но ничего не выскакивает.   -  person John Zwinck    schedule 04.12.2019
comment
@JohnZwinck на самом деле я не могу полностью следовать рассуждениям OP, я не думаю, что здесь что-то const, но я действительно понятия не имею, что на самом деле происходит   -  person 463035818_is_not_a_number    schedule 04.12.2019
comment
Что не так в моем понимании? Вы полагаете, что операторы приведения каким-то образом создают ссылки, как если бы ссылки были объектами, которые были созданы или уничтожены.   -  person Language Lawyer    schedule 04.12.2019
comment
См. eel.is/c++draft/class. Contemporary#6. . Продление срока службы в вашем коде является правильным поведением, потому что инициализатор ссылки const_cast (6.6.1) применяется к static_cast (6.6.2), который запускает временную материализацию (6.1)   -  person Language Lawyer    schedule 04.12.2019


Ответы (2)


Любая ссылка может продлить время жизни объекта. Однако неконстантная ссылка не может быть привязана к временной, как в вашем примере. Расширение Microsoft, на которое вы ссылаетесь, - это не «Продлить время жизни неконстантными ссылками», а «Разрешить неконстантным ссылкам привязываться к временным ссылкам». У них есть это расширение для обратной совместимости с их собственными предыдущими сломанными версиями компилятора.

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

Дополнительная информация: Ссылки на * non * -const продлевают жизнь временные?

person John Zwinck    schedule 04.12.2019
comment
Интересно. Таким образом, расширение Microsoft должно разрешить привязку временных файлов только к неконстантным ссылкам, а не изменять способ работы самого продления времени жизни. Что насчет константности базового объекта, правильно ли мое предположение, что она действительно константна? - person Steelbadger; 04.12.2019
comment
Ваш объект не является константой. Например, вы можете позвонить ptr.reset(...). - person John Zwinck; 04.12.2019
comment
@JohnZwinck Объект, материализованный static_cast<const std::shared_ptr<int>&> , является const. - person Language Lawyer; 04.12.2019
comment
@LanguageLawyer: Вы соответствуете своему имени. Да, объект, материализованный указанным вами приведением, - это const. Но то, что я бы назвал базовым объектом (сам shared_ptr, а не ссылка), не является константой и не является его самой внешней ссылкой. - person John Zwinck; 05.12.2019
comment
@JohnZwinck Вы имеете в виду, что в обычной реализации этот объект создается в записываемом хранилище, поэтому вы можете изменить его, как если бы объект не был константным. - person Language Lawyer; 05.12.2019

Связанная статья явно неверна. Временный объект не обязательно (обязательно) константный. Тот факт, что он не может быть привязан к неконстантной референции, не имеет значения. Он может быть привязан к ссылке rvalue без необходимости приведения и может быть изменен с помощью такой ссылки. В этом нет УБ. Также можно временно вызывать неконстантные функции-члены. Вся концепция семантики перемещения основана на этом факте.

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

person n. 1.8e9-where's-my-share m.    schedule 04.12.2019