Почему выражение i = ++i + i++ допустимо в constexpr функции?

На cppreference столкнулся с утверждением, что i = ++i + i++ это ub, вспомнив, что на этапе компиляции вроде как не может быть выполнен код с ub решил проверить накидав несложный код:

#include <iostream>

constexpr int func(int i){
    i = ++i + i++;
    return i;
}

constexpr int glob_i = func(1);

int main()
{
    std::cout << glob_i << "\n"; // выводит 4 
    return 0;
}

Никак не могу разобраться, почему это не приводит к ошибке компиляции? И если уж это компилируется, почему аналогичные вычисления в рантайме дают результат 5?

Пример на godbolt: https://godbolt.org/z/rM8b7szz5
Статья на cppreference откуда все началось https://en.cppreference.com/w/cpp/language/eval_order


Ответы (1 шт):

Автор решения: Intelligent Shade of Blue

Согласно [expr.const] §10.8 если выражение вызывает НП (неопределенное поведение), то оно перестает быть constexpr выражением:

(10) Выражение E является основным постоянным выражением, если при оценке E, следуя правилам абстрактной машины ([intro.execution]), не будет получено одно из следующих:

...

(10.8) — Операция, которая привела бы к неопределенному или ошибочному поведению, как указано в [intro] до [cpp].

А также в [intro.defs] §3.65 говорится что:

Оценка постоянного выражения ([expr.const]) никогда не приводит к поведению, явно указанному как неопределенное в [intro] до [cpp].

Т.е. тот факт что ваша constexpr функция вызывает НП, не означает что это должно привести к ошибке компиляции per se. Она просто перестанет быть constexpr (т.е. будет работать только в runtime).

Но, если вы попытаетесь присвоить значение этой функции constexpr переменной, то это уже должно вызвать ошибку компиляции. Например:

constexpr auto f(const int* p) { return *p; }

int main()
{
    auto a = f(nullptr);           // OK, but runtime UB
    constexpr auto b = f(nullptr); // ERROR
    return a + b;
}

Как мы видим все 3 главных компилятора выдают ошибку: https://godbolt.org/z/4bzd8YvvG

Но компилятор не всегда может достоверно определить, является ли поведение функции НП. Я подозреваю это и происходит в вашем случае. Возьмем упрощенную версию:

constexpr auto f(int i)
{
    i = ++i + i++;
    return i;
}

int main()
{
    auto a = f(42);           // OK, but runtime UB
    constexpr auto b = f(42); // should be ERROR
    return a + b;
}

Как ни странно все 3 компилятора работают: https://godbolt.org/z/Ta8j6Tsz9

То есть, в первом примере компилятор может точно определить НП, а во втором нет. Поэтому gcc и clang просто выдают диагностическое сообщение, а msvc вообще молчит...

→ Ссылка