Почему деление нуля с плавающей запятой запрещено в constexpr?

Используя компилятор g++, оба они компилируются нормально:

constexpr double d1 = 2.0 / 1.0;
const double d2 = 2.0 / 0.0;

Но это не так:

constexpr double d2 = 2.0 / 0.0;

Ошибка времени компиляции:

error: ‘(2.0e+0 / 0.0)’ is not a constant expression

Обратите внимание, что cppreference приводит именно этот пример, подтверждая, что это запрещено.

Я не понимаю, почему это не разрешено. Учитывая, что std::numeric_limits::is_iec559 равен constexpr, компилятор четко знает во время компиляции, в пределах constexpr, разрешено ли деление на ноль. Итак, если это разрешено... почему это не разрешено в constexpr?

Обновлено: Было высказано предположение, что этот вопрос аналогичен Поведению деления с плавающей запятой на ноль. Мой вопрос здесь более целенаправленный (почему первые два выражения кажутся разрешенными, а третье — запрещенным)? Ответы на другой вопрос, кажется, не проливают на это света: некоторые ответы подразумевают, что все три выражения являются UB (и, следовательно, запрещены? Я не думаю, что верю в это; но в любом случае есть что-то конкретное). о constexpr, который, кажется, имеет значение, поэтому этот вопрос сосредоточен на constexpr).

Этот вопрос также похож на деление с плавающей запятой во время компиляции на ноль в C++, принятый ответ которого (по сути, «это UB, так что все подходит») вообще не помогает.

Деление на ноль — это неопределенное поведение в C++, даже если ваш компилятор использует IEEE754, который определяет такое поведение.

Yksisarvinen 01.07.2024 10:27

@Yksisarvinen нет, это уже не (обязательно) UB; на самом деле это почти никогда не UB. См. stackoverflow.com/questions/42926763/…

Don Hatch 01.07.2024 10:29

Я не могу сказать, что имел в виду cppreference, но стандарт C++ явно говорит, что это неопределенное поведение, без каких-либо условий. eel.is/c++draft/expr.mul#4. И UB не допускается в константных выражениях.

Yksisarvinen 01.07.2024 10:35

@DonHatch связанный ответ относится к C документации en.cppreference.com/w/c/language/operator_arithmetic . Я думаю, это место, где C++ и C расходятся.

Richard Critten 01.07.2024 10:40

@Yksisarvinen Интересно. Я предполагал, что cppreference правильно суммирует недавнюю (более позднюю, чем c++11) версию спецификации c++. Возможно нет? Или, возможно, имеется в виду какой-то другой раздел. Теперь я снова в замешательстве.

Don Hatch 01.07.2024 10:44

@DonHatch Как заметил Ричард, вы нашли страницу для языка C. На странице C++ написано: «Если rhs равно нулю, поведение не определено», а затем приступает к объяснению поведения деления на ноль, если std::numeric_limits::is_iec559 истинно... Я не уверен, какое правило здесь задумано, но, как написано, ваш компилятор правильно отклоняет деление на ноль в constexpr.

Yksisarvinen 01.07.2024 10:50

Также: «7.7 Константные выражения... 5 Выражение E является основным постоянным выражением, если только вычисление E, следуя правилам абстрактной машины (6.9.1), не приведет к вычислению одного из следующих:... (5.7) — операция, которая будет иметь неопределенное поведение, как указано в пунктах 4–15 настоящего документа [Примечание: включая, например, переполнение целых чисел со знаком (7.2), определенную арифметику указателей (7.6.6), деление на ноль (7.6.5) ) или определенные операции сдвига (7.6.7) — последнее примечание] ;" Довольно явно

Oersted 01.07.2024 11:08

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

Oersted 01.07.2024 11:15

cppreference кажется немного оптимистичным по отношению к IEEE 754: из этого стандарта: 7.3 Деление на ноль 7.3.0 Исключение dividByZero должно сигнализироваться тогда и только тогда, когда для операции с конечными операндами определен точный бесконечный результат. Результатом по умолчанию divByZero должен быть знак ∞, правильно подписанный в соответствии с операцией: - Для деления, когда делитель равен нулю, а делимое представляет собой конечное ненулевое число, знак бесконечности представляет собой исключающее ИЛИ знаков операндов. (см. 6.3)... Я не до конца понимаю смысл. 0/0 там не упоминается

Oersted 01.07.2024 11:21

@Эрстед относительно отличия от другого вопроса: я сделал «Обновлено:», пытаясь объяснить. Надеюсь, это имело какой-то смысл.

Don Hatch 01.07.2024 11:30

Действительно, для первого предложенного обмана, а также: stackoverflow.com/questions/30459773/… , stackoverflow.com/questions/68970223/…?

Oersted 01.07.2024 11:34

@Oersted Ах, я понимаю. Да, похоже, тот же вопрос. Но принятый ответ на этот вопрос мне мало что проясняет. С другой стороны, я думаю, обсуждение в этой ветке комментариев начинает помогать мне понимать. Не знаю, как действовать дальше. Может быть, если я получу достаточно ясности, я (или кто-то другой) смогу написать четкий и полный ответ на другой вопрос и пометить этот как дубль?

Don Hatch 01.07.2024 11:38

Другой способ увидеть «проблему»: godbolt.org/z/7q6x3Y45E. Именно контекст вычисления константы по праву вызывает ошибку компиляции. В моем фрагменте первая строка (не оцениваемая константой) компилируется, но, я думаю, формально все еще является UB.

Oersted 01.07.2024 11:39

@DonHatch: кажется, это правильный путь. Насколько я понимаю, деление на ноль — это UB, и точка. IEEE754 определяет только конечное (!= 0)/0, но не 0/0. Вероятно, это спорно, но на данный момент мой вывод таков: 1- поведение компиляторов соответствует стандарту; 2- формулировка cppref неверна; 3- никогда не полагаться на /0 в своем коде.

Oersted 01.07.2024 11:44

кстати: godbolt.org/z/747abWT7z gcc и clang обрабатывают 0/0 по-разному (-nan и nan)

Oersted 01.07.2024 11:50

Что касается страницы cppreference, представленной ранее, если плавающая запятая соответствует стандарту IEEE 754, деление на ноль приводит либо к FE_INVALID, либо к FE_DIVBYZERO. Если math_errhandling имеет MATH_ERREXCEPT, это вызывает исключение с плавающей запятой. Исключения в constexpr не допускаются. Если для math_errhandlingMATH_ERREXCEPT установлено значение 0, то исключений нет и можно отправить отчет об ошибке.

Red.Wave 01.07.2024 12:00

@Red.Wave О! Хорошо, я думаю, вы натолкнулись на одно из упомянутых кем-то объяснений, которое на самом деле является для меня убедительным (в отличие от любого из предыдущих аргументов в форме «оно запрещено из-за следующих непонятных немотивированных и, возможно, устаревших отрывков в спецификации C++, чьи содержимое я даже не могу прочитать и должен догадываться из того, что говорит cppreference"). Итак, для меня это убедительно: хотя компилятор знает, что IEEE754 поддерживается, компилятор на самом деле не может просто оценить 1/0 как +бесконечность, потому что на самом деле это может не быть ответом, в зависимости от настроек времени выполнения.

Don Hatch 01.07.2024 12:12
math_errhandling — это макрос, который расширяется до целочисленного значения основной константы. Я имею в виду, что если эта константа времени компиляции имеет MATH_ERREXCEPT, то во время компиляции мы знаем, что деление с плавающей запятой на .0 вызывает выброс/повышение. В противном случае мы знаем, что исключений не будет. Таким образом, в первом случае компилятор не должен принимать деление на .0 во время компиляции, а во втором — должен. В противном случае компилятор ведет себя неправильно. Более того, что интересно, в cppreference не указан случай операндов разных типов; поэтому я воздерживаюсь от простого термина div на 0.
Red.Wave 01.07.2024 12:53

(то есть... при условии, что IEEE754 поддерживается)

Don Hatch 01.07.2024 13:14

@DonHatch Что бы ни означало исключение, это ненормальное завершение программы. А этого не может произойти во время компиляции. По сути, это означает, что деления на 0 быть не может constexpr. Я изначально пропустил абзац, на который вы указали. Спасибо за примечание.

Red.Wave 01.07.2024 14:07

Этот вопрос похож на: Деление плавающей запятой во время компиляции на ноль в C++. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.

Fedor 02.07.2024 17:12

@Федор, я добавил правку. Этот вопрос на самом деле не отличается от этого, но (как это часто бывает) у другого есть принятый ответ, который, по моему мнению, вообще ничего не решает, поэтому кажется, что потенциал для получения чего-то полезного от этого вопроса есть. застопорился. Напротив, этот текущий вопрос вызывает дискуссию и ответ, который (наряду с текущим/недавним обсуждением в stackoverflow.com/questions/42926763/…) действительно помогает мне понять.

Don Hatch 03.07.2024 00:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
24
246
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Стандарт ISO C++ явно определяет, что деление на ноль имеет неопределенное поведение как для целочисленных типов, так и для типов с плавающей запятой.

См. [expr.mul]/4.

Если вычисление выражения будет иметь неопределенное поведение в соответствии со спецификацией основного языка, то выражение, вычисление которого будет включать в себя это вычисление, указывается как не являющееся (основным) константным выражением.

Поэтому любое деление на ноль должно быть невозможно во время компиляции.

Хотя деление на ноль не определено стандартом C++, обычно реализации типов с плавающей запятой соответствуют стандарту IEEE 754, который определяет результат деления на ноль. Это, конечно, хорошо. Если программа имеет неопределенное поведение в соответствии со стандартом C++, то компилятор волен придавать ей любую семантику. В частности, он может пообещать соблюдать более строгие правила, например: другая спецификация.

Однако внутри константных выражений язык намеренно ограничен тем, что явно определено стандартом. Компиляторы не могут расширить поведение constexpr так, как это возможно во время выполнения. Если инициализация переменной constexpr не является константным выражением, программа имеет неверный формат.

Хотя стандарт никогда не требует от компилятора сбоя компиляции. Если программа неправильно сформирована, единственным требованием является то, что компилятор должен выдать одну диагностику. С таким же успехом это может быть просто предупреждением, и тогда компилятор сможет просто выполнить деление во время компиляции так же, как и во время выполнения.

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

Тот факт, что это не постоянное выражение, может быть использован в правильно построенной программе для SFINAE. В противном случае соответствующий компилятор мог бы разрешить это с предупреждением (GCC делает это для некоторых преобразований, которые формально сужают, но очевидно безопасны), но такого рода несогласованность может пугать.

Davis Herring 02.07.2024 01:45

«Это, конечно, хорошо». :-) На самом деле это очень важный момент, который вообще не является общепризнанным. Распространенное (неправильное) понимание, похоже, состоит в том, что на самом деле это не так, то есть UB означает, что соответствующий компилятор не может гарантировать более строгую спецификацию. Таким образом, многие разговоры о UB преждевременно заканчиваются словами «это UB, так что все пойдет», что совершенно бесполезно stackoverflow.com/questions/68970223/… Спасибо за более детальное объяснение; принимая.

Don Hatch 03.07.2024 00:41

Другие вопросы по теме