Let's make this example simpler (and probably cppreference should do the same):
int f(); // not constexpr, not even defined
void test() {
static const int a = f();
constexpr const int& ra = a; // OK: a is a glvalue constant expression
constexpr int ia = a; // Error: a is not a prvalue constant expression
}
It's true that f()
-- or, in the original example, std::random_device{}()
-- is not a constant expression. But we don't need it to be to initialize ra
. Because we're not reading a
, we're just binding a reference to it -- all we need to bind a constexpr reference to a
is for a
to have static storage duration, which it does. So that's fine.
But reading a
as a constant is not allowed, that's what the next line is indicating: we can't initialize ia
because a
isn't a prvalue constant expression (specificaly, we're violating the rule that we can't apply an lvalue-to-rvalue conversion becuase a
isn't usable in constant expressions, which is currently [expr.const]/5.9).
That's basically the distinction between ra
and ia
: ra
just needs a
to have a stable address, because that's the part that we need to be constant. But ia
needs a
to have a constant value. This might be even more obvious if we did:
void test()
{
static const int a = f();
constexpr const int& ra = a; // OK
constexpr const int* pa = &a; // OK
constexpr int ia = a; // Error
}
ra
and pa
are equivalent - the only thing we care about is the address of a
, not the value of a
. The value of a
isn't constant, but the address is, so it works.