Здравствуйте, CreatorCray, Вы писали:
S>>Хм. И в каком месте стандарта это разрешено?
CC>Ни в каком месте стандарта не описано как оно вообще должно работать.
Эмм, "как оно вообще должно работать" написано в стандарте очень и очень подробно.
Вычисления в компайл-тайме — это только один из видов оптимизаций, которые может делать компилятор.
И эти оптимизации не могут нарушать стандарт. Поэтому, если в стандарте написано "компилятор имеет право самопроизвольно увеличивать разрядность prvalue до int", то компилятор имеет право это делать, хоть в рантайме, хоть в компайл тайме. А если ему чего-то делать нельзя — например, увеличивать разрядность int prvalue, то он не может этого делать ни в рантайме, ни в компайл-тайме.
Оптимизатору запрещено
изменять семантику программы. Единственное место, где ему можно резвиться на эту тему — это как раз UB.
И как раз потому, что семантика UB операций
не определена никак. Поэтому программа, которая выводит "oops", с т.з. стандарта ровно настолько же корректна, как и та, которая выводит "wow".
CC>Потому и пишут эти "оптимизаторы" абстрактную символьную логику, совершенно не привязанную к backend.
Семантика абстрактной символьной логики жёстко задана. В частности, в LLVM есть специальные инструкции для моделирования UB, и специальные конструкции для того, чтобы описывать недетерминированные программы.
Добавлено всё это ровно для того, чтобы можно было получать говноэффекты вроде обсуждаемого в данном треде для С++ программ. Ни одному другому известному мне языку это даром не надо; и в LLVM, естественно, есть инструкции знаковой арифметики, поведение которых well-defined.
CC>Для генерируемого кода, но не для AST трансформаций.
Это одно и то же.
CC>Вот тут лежит огроменный болт, ибо frontend пишется вообще без оглядки на платформу.
Эмм, есть примерно два класса компиляторов. Один — это платформенно-специфичные, вроде того же intel classic. У них нет никакой "без оглядки на платформу", потому что платформа им хорошо известна.
И другой класс — это LLVM-фронты, вроде кланга, современного gcc, и современного icx. Они никаких "символьных вычислений" не делают, т.к. это не их проблема. Все оптимизации, которые они выполняют — платформенно-независимы.
А все вот эти символьные преобразования, которые вас так пугают, делаются внутри оптимизатора LLVM. И там никогда-никогда не бывает так, что мы считаем среднее двух интов и вдруг мы решили его вычислить через лонг, и в компайл-тайме переполнения не произошло. А в другом месте мы его вычисление отложили до рантайма, и получилось переполнение.
CC>>>То, что ты в данном примере называешь UB — на самом деле очень даже defined behavior. Неожиданный — да, но не undefined.
S>>Простите, какой именно эффект?
CC>Compile time вычислений.
Нет, этот эффект именно что undefined.
S>>Если бы знаковое переполнение было implementation-defined, то ни с какими флагами компилятор бы не выдавал oops ни на какой платформе.
CC>См выше про frontend
Это нерелевантные рассуждения. Фронтенд по факту выполняется на той же платформе, что и результат.
Посмотрите на беззнаковые целые — там ни при каких флагах никаких косяков не возникает. Почему-то фронтенд не ошибается при compile-time вычислениях беззнаковых. Чем, собственно, вы и воспользовались в вашем решении.