Сообщение Re[106]: Когда это наконец станет defined behavior? от 22.08.2023 12:19
Изменено 23.08.2023 11:56 vdimas
Re[106]: Когда это наконец станет defined behavior?
Здравствуйте, vopl, Вы писали:
V>глобальность непричем, вот такой кейс имеет то же самое "гипотетическое UB"
V>
В этом случае не получится ни объяснить это UB, ни продемонстрировать потенциальный кейз, где UB может себя проявить.
Оно возможно только в фазе неоконченной инициализации модуля, но на такое UB можно попасть и при простом статическом const int i = calcValue();
V>>Для локальных констант оно невоспроизводимо, т.к. доступ к константе происходит только после инициализации переменной.
V>не важно когда происходит доступ.
Важно, согласно определению lifetime, да и просто здравого смысла.
V>Тип объекта, определяемый декларацией потенциально является константным.
Но что-то выполнить в коде и пронаблюдать потенциальное неопределённое поведение можно только после объявления константы, где объявление локальной константы по стандарту совмещено с определением.
Этот момент и не даёт продемонстрировать неопределённость на практике.
Исключение составляет extern const, но это опять заход на новый круг про глобальные переменные. ))
V>На этой основе компилятор может делать всякие предположения относительно доступа к этому объекту. И объект после своего создания/инициализации используется как мутабельный.
В твоём примере объект outer считается не созданным до возврата из make.
V>вот так оно может быть развернуто компилятором
V>
Не может и не будет. ))
Там будет другое:
— sub RSP на необходимую величину для размещения всех локальных переменных;
— присвоить регистру EDI адрес outer (допустим, значение RSP+16);
— call make с параметром в регистре EDI;
— считать outer инициализированным.
А если указать флаг оптимизации, то будет простое использование константы 220, безо-всяких переменных, объектов и ф-ий.
V>наверное да.. Я игнорирую тезисы которые просто высказываются в утвердительной форме, без обоснований
Основание можно спросить, т.к. вам тут многое говорится как "и так известное", и окружающие ХЗ что именно непонятно.
V>>Что характерно, в статической фазе константе const int i будет присвоено значение 0, а в динамической фазе эта константа будет перезаписана результатом вызова calValue(). Не смущает, ы? ))
V>это все не важно
Было бы не важно, но вы пока мест показали именно такое UB, для демонстрации которого не требовался ни RVO/NRVO, ни даже нетривиальные типы данных.
Достаточно было простого const int i = calcValue();, и сей UB живёт с самых первых версий С++ 80-х годов.
И будет жить вечно, походу. ))
И такие вещи упоминаются как сами собой разумеющиеся, т.е. не требующие обоснования, бо относятся к минимальному бэкгрраунду для С++.
V>>Так вот, услышьте уже, что доступ к неинициализированным данным модуля является UB и безо-всякого RVO/NRVO, т.е. это другое UB, нерелевантное обсуждаемому.
V>Услышал. Сабжевое "гипотетическое UB" — не про доступ к неинициализированным данным.
Но показать не можете. ))
Пока что была "демонстрация" только для глобальный переменной.
А для локальной ты никак не перепрыгнешь обязательность инициализации константы при объявлении.
V>В кейсе с "гипотетическим UB" все инициализировано до использования.
До использования константной переменной.
Ты всерьёз думаешь, что никто не понял ваш аргумент про "фактическое слияние" одного объекта с другим?
Любая фактическая подмена, которая не нарушает семантику, является "не важной" для рассуждений, до тех пор, пока язык даёт свои гарантии.
А он их даёт, т.к. переменная outer может быть использована только после того, как переменная inner закончила свой жизненный цикл.
А тот факт, что обе переменные обозначали одну и ту же область памяти — это и есть суть RVO оптимизации (не обязательно, кстате, было заострять на более узком кейзе NRVO, бо оно попахивало очередным непониманием материала).
V>Это был пример в котором объявлен константный объект, он создан/инициализирован
Не создан и не инициализирован.
Более того, мог быть создан объект совсем другого типа!
Простой пример:
Для всех 3-х объектов будет идентичный код в случае инлайна конструтора копирования от Base.
V>и потом с ним идет работа как не с константным, что есть UB.
Наоборот, сначала идёт работа как с неконстантным, а потом этот объект становится доступным для операций на вызывающей стороне, приобретая константность.
У тебя нет возможность использовать переменную outer до возврата из make.
V>>Но после динамической фазы инициализации модуля, либо же, в этой фазе, но с учётом гарантированной последовательности инициализации глобальных переменных модуля, можно избежать упомянутого мною UB, и RVO/NRVO опять на него никак не влияют. Всё.
V>Не в ту сторону все.
А ничего другого, показывающего некий UB, вы так и не показали.
V>Пример посмотрел, понял, там отписал. В нем другой способ инициирования записи в константный объект, в общем, этот пример не принимается как полный, но очень хорошо показывает запись в константный объект, которая есть и в кейсе с "гипотетическим UB"
Вообще-то, тот пример намекал на возможность подобной утечки и через buildMap(), если адрес локальной переменной result куда-то протечёт как неконстантный.
Но! Утекание адреса локальной переменной является UB еще до рассждений об RVO, такое утекание — грубейшая ошибка!
Не везёт вам... ))
Итого, мы упомянули уже три известных UB:
1. UB с доступом к неинициализированному значению при инициализации глобальных переменных.
Решение: обращаться к глобальным переменным модуля в фазе динамической инициализации необходимо согласно гарантированному порядку их инициализации.
К переменным из других модулей можно обращаться только по окончании фазы динамической инициализации (т.е. после вызова main)
2. UB с константностью при наличии побочных эффектов в конструкторах.
Решение:
— такие объекты не стоит делать константами;
— такие объекты не стоит делать перемещаемыми/копируемыми.
3. UB при утекании адресов/ссылок на локальные переменные.
Решение: не давать им утекать, это грубая ошибка.
Но не видели еще работающего примера с демонстрацией UB вокруг константности, вызванного исключительно RVO!
Т.е. когда о любых других UB речи не идёт.
И так, пример будет?
(желательно с объяснением, конечно, бо упомянутые 3 UB объяснить проще простого)
V>глобальность непричем, вот такой кейс имеет то же самое "гипотетическое UB"
V>
V>struct T {int m;};
V>T make()
V>{
V> T inner;
V> inner.m = 220;
V> return inner;
V>}
V>int main()
V>{
V> const T outer = make();
V> return 0;
V>}В этом случае не получится ни объяснить это UB, ни продемонстрировать потенциальный кейз, где UB может себя проявить.
Оно возможно только в фазе неоконченной инициализации модуля, но на такое UB можно попасть и при простом статическом const int i = calcValue();
V>>Для локальных констант оно невоспроизводимо, т.к. доступ к константе происходит только после инициализации переменной.
V>не важно когда происходит доступ.
Важно, согласно определению lifetime, да и просто здравого смысла.
V>Тип объекта, определяемый декларацией потенциально является константным.
Но что-то выполнить в коде и пронаблюдать потенциальное неопределённое поведение можно только после объявления константы, где объявление локальной константы по стандарту совмещено с определением.
Этот момент и не даёт продемонстрировать неопределённость на практике.
Исключение составляет extern const, но это опять заход на новый круг про глобальные переменные. ))
V>На этой основе компилятор может делать всякие предположения относительно доступа к этому объекту. И объект после своего создания/инициализации используется как мутабельный.
В твоём примере объект outer считается не созданным до возврата из make.
V>вот так оно может быть развернуто компилятором
V>
V>struct T {int m;};
V>int main()
V>{
V> count T outer{};// только конструктор будет от неконстантного имени. Да я знаю что конструктору плевать, просто отмечаю сей факт
V> T& inner = const_cast<T&>(outer);
V> inner.m = 220;
V> return 0;
V>}Не может и не будет. ))
Там будет другое:
— sub RSP на необходимую величину для размещения всех локальных переменных;
— присвоить регистру EDI адрес outer (допустим, значение RSP+16);
— call make с параметром в регистре EDI;
— считать outer инициализированным.
А если указать флаг оптимизации, то будет простое использование константы 220, безо-всяких переменных, объектов и ф-ий.
V>наверное да.. Я игнорирую тезисы которые просто высказываются в утвердительной форме, без обоснований
Основание можно спросить, т.к. вам тут многое говорится как "и так известное", и окружающие ХЗ что именно непонятно.
V>>Что характерно, в статической фазе константе const int i будет присвоено значение 0, а в динамической фазе эта константа будет перезаписана результатом вызова calValue(). Не смущает, ы? ))
V>это все не важно
Было бы не важно, но вы пока мест показали именно такое UB, для демонстрации которого не требовался ни RVO/NRVO, ни даже нетривиальные типы данных.
Достаточно было простого const int i = calcValue();, и сей UB живёт с самых первых версий С++ 80-х годов.
И будет жить вечно, походу. ))
И такие вещи упоминаются как сами собой разумеющиеся, т.е. не требующие обоснования, бо относятся к минимальному бэкгрраунду для С++.
V>>Так вот, услышьте уже, что доступ к неинициализированным данным модуля является UB и безо-всякого RVO/NRVO, т.е. это другое UB, нерелевантное обсуждаемому.
V>Услышал. Сабжевое "гипотетическое UB" — не про доступ к неинициализированным данным.
Но показать не можете. ))
Пока что была "демонстрация" только для глобальный переменной.
А для локальной ты никак не перепрыгнешь обязательность инициализации константы при объявлении.
V>В кейсе с "гипотетическим UB" все инициализировано до использования.
До использования константной переменной.
Ты всерьёз думаешь, что никто не понял ваш аргумент про "фактическое слияние" одного объекта с другим?
Любая фактическая подмена, которая не нарушает семантику, является "не важной" для рассуждений, до тех пор, пока язык даёт свои гарантии.
А он их даёт, т.к. переменная outer может быть использована только после того, как переменная inner закончила свой жизненный цикл.
А тот факт, что обе переменные обозначали одну и ту же область памяти — это и есть суть RVO оптимизации (не обязательно, кстате, было заострять на более узком кейзе NRVO, бо оно попахивало очередным непониманием материала).
V>Это был пример в котором объявлен константный объект, он создан/инициализирован
Не создан и не инициализирован.
Более того, мог быть создан объект совсем другого типа!
Простой пример:
#include <iostream>
struct Base {
int value;
char filler[256];
};
Base make() {
Base result;
result.value = 42;
return result;
}
struct Derived : Base {
Derived(const Base & other) {
value = other.value;
}
};
struct SomeObj {
int value;
SomeObj(const Base & other) {
value = other.value;
}
};
int main() {
const Base outer1 = make();
std::cout << outer1.value;
const Derived outer2 = make();
std::cout << outer2.value;
const SomeObj outer3 = make();
std::cout << outer3.value;
return 0;
}Для всех 3-х объектов будет идентичный код в случае инлайна конструтора копирования от Base.
V>и потом с ним идет работа как не с константным, что есть UB.
Наоборот, сначала идёт работа как с неконстантным, а потом этот объект становится доступным для операций на вызывающей стороне, приобретая константность.
У тебя нет возможность использовать переменную outer до возврата из make.
V>>Но после динамической фазы инициализации модуля, либо же, в этой фазе, но с учётом гарантированной последовательности инициализации глобальных переменных модуля, можно избежать упомянутого мною UB, и RVO/NRVO опять на него никак не влияют. Всё.
V>Не в ту сторону все.
А ничего другого, показывающего некий UB, вы так и не показали.
V>Пример посмотрел, понял, там отписал. В нем другой способ инициирования записи в константный объект, в общем, этот пример не принимается как полный, но очень хорошо показывает запись в константный объект, которая есть и в кейсе с "гипотетическим UB"
Вообще-то, тот пример намекал на возможность подобной утечки и через buildMap(), если адрес локальной переменной result куда-то протечёт как неконстантный.
Но! Утекание адреса локальной переменной является UB еще до рассждений об RVO, такое утекание — грубейшая ошибка!
Не везёт вам... ))
Итого, мы упомянули уже три известных UB:
1. UB с доступом к неинициализированному значению при инициализации глобальных переменных.
Решение: обращаться к глобальным переменным модуля в фазе динамической инициализации необходимо согласно гарантированному порядку их инициализации.
К переменным из других модулей можно обращаться только по окончании фазы динамической инициализации (т.е. после вызова main)
2. UB с константностью при наличии побочных эффектов в конструкторах.
Решение:
— такие объекты не стоит делать константами;
— такие объекты не стоит делать перемещаемыми/копируемыми.
3. UB при утекании адресов/ссылок на локальные переменные.
Решение: не давать им утекать, это грубая ошибка.
Но не видели еще работающего примера с демонстрацией UB вокруг константности, вызванного исключительно RVO!
Т.е. когда о любых других UB речи не идёт.
И так, пример будет?
(желательно с объяснением, конечно, бо упомянутые 3 UB объяснить проще простого)
Re[106]: Когда это наконец станет defined behavior?
Здравствуйте, vopl, Вы писали:
V>глобальность непричем, вот такой кейс имеет то же самое "гипотетическое UB"
V>
В этом случае не получится ни объяснить это UB, ни продемонстрировать потенциальный кейз, где UB может себя проявить.
Оно возможно только в фазе неоконченной инициализации модуля, но на такое UB можно попасть и при простом статическом const int i = calcValue();
V>>Для локальных констант оно невоспроизводимо, т.к. доступ к константе происходит только после инициализации переменной.
V>не важно когда происходит доступ.
Важно, согласно определению lifetime, да и просто здравого смысла.
V>Тип объекта, определяемый декларацией потенциально является константным.
Но что-то выполнить в коде и пронаблюдать потенциальное неопределённое поведение можно только после объявления константы, где объявление локальной константы по стандарту совмещено с определением.
Этот момент и не даёт продемонстрировать неопределённость на практике.
Исключение составляет extern const, но это опять заход на новый круг про глобальные переменные. ))
V>На этой основе компилятор может делать всякие предположения относительно доступа к этому объекту. И объект после своего создания/инициализации используется как мутабельный.
В твоём примере объект outer считается не созданным до возврата из make.
V>вот так оно может быть развернуто компилятором
V>
Не может и не будет. ))
Там будет другое:
— sub RSP на необходимую величину для размещения всех локальных переменных;
— присвоить регистру EDI адрес outer (допустим, значение RSP+16);
— call make с параметром в регистре EDI;
— считать outer инициализированным.
А если указать флаг оптимизации, то будет простое использование константы 220, безо-всяких переменных, объектов и ф-ий.
V>наверное да.. Я игнорирую тезисы которые просто высказываются в утвердительной форме, без обоснований
Основание можно спросить, т.к. вам тут многое говорится как "и так известное", и окружающие ХЗ что именно непонятно.
V>>Что характерно, в статической фазе константе const int i будет присвоено значение 0, а в динамической фазе эта константа будет перезаписана результатом вызова calValue(). Не смущает, ы? ))
V>это все не важно
Было бы не важно, но вы пока мест показали именно такое UB, для демонстрации которого не требовался ни RVO/NRVO, ни даже нетривиальные типы данных.
Достаточно было простого const int i = calcValue();, и сей UB живёт с самых первых версий С++ 80-х годов.
И будет жить вечно, походу. ))
И такие вещи упоминаются как сами собой разумеющиеся, т.е. не требующие обоснования, бо относятся к минимальному бэкгрраунду для С++.
V>>Так вот, услышьте уже, что доступ к неинициализированным данным модуля является UB и безо-всякого RVO/NRVO, т.е. это другое UB, нерелевантное обсуждаемому.
V>Услышал. Сабжевое "гипотетическое UB" — не про доступ к неинициализированным данным.
Но показать не можете. ))
Пока что была "демонстрация" только для глобальный переменной.
А для локальной ты никак не перепрыгнешь обязательность инициализации константы при объявлении.
V>В кейсе с "гипотетическим UB" все инициализировано до использования.
До использования константной переменной.
Ты всерьёз думаешь, что никто не понял ваш аргумент про "фактическое слияние" одного объекта с другим?
Любая фактическая подмена, которая не нарушает семантику, является "не важной" для рассуждений, до тех пор, пока язык даёт свои гарантии.
А он их даёт, т.к. переменная outer может быть использована только после того, как переменная inner закончила свой жизненный цикл.
А тот факт, что обе переменные обозначали одну и ту же область памяти — это и есть суть RVO оптимизации (не обязательно, кстате, было заострять на более узком кейзе NRVO, бо оно попахивало очередным непониманием материала).
V>Это был пример в котором объявлен константный объект, он создан/инициализирован
Не создан и не инициализирован.
Более того, мог быть создан объект совсем другого типа!
Простой пример:
Для всех 3-х объектов будет идентичный код в случае инлайна конструтора копирования от Base.
V>и потом с ним идет работа как не с константным, что есть UB.
Наоборот, сначала идёт работа как с неконстантным, а потом этот объект становится доступным для операций на вызывающей стороне, приобретая константность.
У тебя нет возможность использовать переменную outer до возврата из make.
V>>Но после динамической фазы инициализации модуля, либо же, в этой фазе, но с учётом гарантированной последовательности инициализации глобальных переменных модуля, можно избежать упомянутого мною UB, и RVO/NRVO опять на него никак не влияют. Всё.
V>Не в ту сторону все.
А ничего другого, показывающего некий UB, вы так и не показали.
V>Пример посмотрел, понял, там отписал. В нем другой способ инициирования записи в константный объект, в общем, этот пример не принимается как полный, но очень хорошо показывает запись в константный объект, которая есть и в кейсе с "гипотетическим UB"
Вообще-то, тот пример намекал на возможность подобной утечки и через buildMap(), если адрес локальной переменной result куда-то протечёт как неконстантный.
Но! Утекание адреса локальной переменной является UB еще до рассждений об RVO, такое утекание — грубейшая ошибка!
Не везёт вам... ))
Итого, мы упомянули уже три известных UB:
1. UB с доступом к неинициализированному значению при инициализации глобальных переменных.
Решение: обращаться к глобальным переменным модуля в фазе динамической инициализации необходимо согласно гарантированному порядку их инициализации.
К переменным из других модулей можно обращаться только по окончании фазы динамической инициализации (т.е. после вызова main)
2. UB с константностью при наличии побочных эффектов в конструкторах.
Решение:
— такие объекты не стоит делать константами;
— такие объекты не стоит делать перемещаемыми/копируемыми.
3. UB при утекании адресов/ссылок на локальные переменные.
Решение: не давать им утекать, это грубая ошибка.
Но не видели еще работающего примера с демонстрацией UB вокруг константности, вызванного исключительно RVO!
Т.е. когда о любых других UB речи не идёт.
Итак, пример будет?
(желательно с объяснением, конечно, бо упомянутые 3 UB объяснить проще простого)
V>глобальность непричем, вот такой кейс имеет то же самое "гипотетическое UB"
V>
V>struct T {int m;};
V>T make()
V>{
V> T inner;
V> inner.m = 220;
V> return inner;
V>}
V>int main()
V>{
V> const T outer = make();
V> return 0;
V>}В этом случае не получится ни объяснить это UB, ни продемонстрировать потенциальный кейз, где UB может себя проявить.
Оно возможно только в фазе неоконченной инициализации модуля, но на такое UB можно попасть и при простом статическом const int i = calcValue();
V>>Для локальных констант оно невоспроизводимо, т.к. доступ к константе происходит только после инициализации переменной.
V>не важно когда происходит доступ.
Важно, согласно определению lifetime, да и просто здравого смысла.
V>Тип объекта, определяемый декларацией потенциально является константным.
Но что-то выполнить в коде и пронаблюдать потенциальное неопределённое поведение можно только после объявления константы, где объявление локальной константы по стандарту совмещено с определением.
Этот момент и не даёт продемонстрировать неопределённость на практике.
Исключение составляет extern const, но это опять заход на новый круг про глобальные переменные. ))
V>На этой основе компилятор может делать всякие предположения относительно доступа к этому объекту. И объект после своего создания/инициализации используется как мутабельный.
В твоём примере объект outer считается не созданным до возврата из make.
V>вот так оно может быть развернуто компилятором
V>
V>struct T {int m;};
V>int main()
V>{
V> count T outer{};// только конструктор будет от неконстантного имени. Да я знаю что конструктору плевать, просто отмечаю сей факт
V> T& inner = const_cast<T&>(outer);
V> inner.m = 220;
V> return 0;
V>}Не может и не будет. ))
Там будет другое:
— sub RSP на необходимую величину для размещения всех локальных переменных;
— присвоить регистру EDI адрес outer (допустим, значение RSP+16);
— call make с параметром в регистре EDI;
— считать outer инициализированным.
А если указать флаг оптимизации, то будет простое использование константы 220, безо-всяких переменных, объектов и ф-ий.
V>наверное да.. Я игнорирую тезисы которые просто высказываются в утвердительной форме, без обоснований
Основание можно спросить, т.к. вам тут многое говорится как "и так известное", и окружающие ХЗ что именно непонятно.
V>>Что характерно, в статической фазе константе const int i будет присвоено значение 0, а в динамической фазе эта константа будет перезаписана результатом вызова calValue(). Не смущает, ы? ))
V>это все не важно
Было бы не важно, но вы пока мест показали именно такое UB, для демонстрации которого не требовался ни RVO/NRVO, ни даже нетривиальные типы данных.
Достаточно было простого const int i = calcValue();, и сей UB живёт с самых первых версий С++ 80-х годов.
И будет жить вечно, походу. ))
И такие вещи упоминаются как сами собой разумеющиеся, т.е. не требующие обоснования, бо относятся к минимальному бэкгрраунду для С++.
V>>Так вот, услышьте уже, что доступ к неинициализированным данным модуля является UB и безо-всякого RVO/NRVO, т.е. это другое UB, нерелевантное обсуждаемому.
V>Услышал. Сабжевое "гипотетическое UB" — не про доступ к неинициализированным данным.
Но показать не можете. ))
Пока что была "демонстрация" только для глобальный переменной.
А для локальной ты никак не перепрыгнешь обязательность инициализации константы при объявлении.
V>В кейсе с "гипотетическим UB" все инициализировано до использования.
До использования константной переменной.
Ты всерьёз думаешь, что никто не понял ваш аргумент про "фактическое слияние" одного объекта с другим?
Любая фактическая подмена, которая не нарушает семантику, является "не важной" для рассуждений, до тех пор, пока язык даёт свои гарантии.
А он их даёт, т.к. переменная outer может быть использована только после того, как переменная inner закончила свой жизненный цикл.
А тот факт, что обе переменные обозначали одну и ту же область памяти — это и есть суть RVO оптимизации (не обязательно, кстате, было заострять на более узком кейзе NRVO, бо оно попахивало очередным непониманием материала).
V>Это был пример в котором объявлен константный объект, он создан/инициализирован
Не создан и не инициализирован.
Более того, мог быть создан объект совсем другого типа!
Простой пример:
#include <iostream>
struct Base {
int value;
char filler[256];
};
Base make() {
Base result;
result.value = 42;
return result;
}
struct Derived : Base {
Derived(const Base & other) {
value = other.value;
}
};
struct SomeObj {
int value;
SomeObj(const Base & other) {
value = other.value;
}
};
int main() {
const Base outer1 = make();
std::cout << outer1.value;
const Derived outer2 = make();
std::cout << outer2.value;
const SomeObj outer3 = make();
std::cout << outer3.value;
return 0;
}Для всех 3-х объектов будет идентичный код в случае инлайна конструтора копирования от Base.
V>и потом с ним идет работа как не с константным, что есть UB.
Наоборот, сначала идёт работа как с неконстантным, а потом этот объект становится доступным для операций на вызывающей стороне, приобретая константность.
У тебя нет возможность использовать переменную outer до возврата из make.
V>>Но после динамической фазы инициализации модуля, либо же, в этой фазе, но с учётом гарантированной последовательности инициализации глобальных переменных модуля, можно избежать упомянутого мною UB, и RVO/NRVO опять на него никак не влияют. Всё.
V>Не в ту сторону все.
А ничего другого, показывающего некий UB, вы так и не показали.
V>Пример посмотрел, понял, там отписал. В нем другой способ инициирования записи в константный объект, в общем, этот пример не принимается как полный, но очень хорошо показывает запись в константный объект, которая есть и в кейсе с "гипотетическим UB"
Вообще-то, тот пример намекал на возможность подобной утечки и через buildMap(), если адрес локальной переменной result куда-то протечёт как неконстантный.
Но! Утекание адреса локальной переменной является UB еще до рассждений об RVO, такое утекание — грубейшая ошибка!
Не везёт вам... ))
Итого, мы упомянули уже три известных UB:
1. UB с доступом к неинициализированному значению при инициализации глобальных переменных.
Решение: обращаться к глобальным переменным модуля в фазе динамической инициализации необходимо согласно гарантированному порядку их инициализации.
К переменным из других модулей можно обращаться только по окончании фазы динамической инициализации (т.е. после вызова main)
2. UB с константностью при наличии побочных эффектов в конструкторах.
Решение:
— такие объекты не стоит делать константами;
— такие объекты не стоит делать перемещаемыми/копируемыми.
3. UB при утекании адресов/ссылок на локальные переменные.
Решение: не давать им утекать, это грубая ошибка.
Но не видели еще работающего примера с демонстрацией UB вокруг константности, вызванного исключительно RVO!
Т.е. когда о любых других UB речи не идёт.
Итак, пример будет?
(желательно с объяснением, конечно, бо упомянутые 3 UB объяснить проще простого)