Isn’t constant evaluation fun?
There are a few places in the language where we try to do constant evaluation and if that fails, we fallback to doing non-constant evaluation. Static initialization is one such place, initializing constant integers is another.
What happens with:
int const i = f();
is that this could be constant evaluation, but it doesn’t necessarily have to be. Because (non-constexpr) constant integers can still be used as constant expressions, if they meet all the other conditions, we have to try. For instance:
const int n = 42; // const, not constexpr
std::array<int, n> arr; // n is a constant expression, this is ok
So try we do – we call f() as a constant expression. In this context, std::is_constant_evaluated() is true, so we hit the branch with the throw and end up failing. Can’t throw during constant evaluation, so our constant evaluation fails.
But then we fallback, and we try again – this time calling f() as a non-constant expression (i.e. std::is_constant_evaluated() is false). This path succeeds, giving us 1, so i is initialized with the value 1. But notably, i is not a constant expression at this point. A subsequent static_assert(i == 1) would be ill-formed because i‘s initializer was not a constant expression! Even though the non-constant initialization path happens to (otherwise) entirely satisfy the requirements of a constant expression.
Note that if we tried:
constexpr int i = f();
This would have failed because we cannot fallback to the non-constant initialization.