Clang rejects the following program:
struct g { static constexpr bool b = true; };
struct h : g { static constexpr bool b = b; }; // clang nope, gcc ok, msvc ok
static_assert(h::b);
My take on this is that Clang tries to initialize variable b by itself. But, since variable b is still uninitialized, reading from it leads to undefined behavior, which is not allowed in a constant expression.
However, GCC and MSVC both accept this program. Suggesting that they simply resolve h::b's initialization value from its base member g::b. Which begs the questions: At what point does h::b starts to hide its base member g::b?
Of course, qualifying b could solve this discrepancy altogether, but that is besides the point of this question.
Demo
Clang's error message:
<source>:2:38: error: constexpr variable 'b' must be initialized by a
constant expression
2 | struct h : g { static constexpr bool b = b; };
| ^ ~
<source>:2:42: note: read of object outside its lifetime is not allowed in a
constant expression
2 | struct h : g { static constexpr bool b = b; };
| ^
Suggesting that they simply resolve h::b's initialization value from its base member g::b. Which begs the questions: At what point does h::b starts to hide its base member g::b?
The locus of the declaration is immediately after the declarator, i.e. here after the b token, before the = token. (see [basic.scope.pdecl]/1)
From that point lookup will be able to find the declared name and so it will hide the base class b. (see [basic.lookup.general]/2 and following)
The second b in static constexpr bool b = b; refers to the member being defined, not to the one in the base class.
The question is whether or not the initialization is well-formed.
First suppose it was not marked constexpr. Then (assuming the proposed resolution of CWG 2821), the initialization would be well-formed and would have well-defined behavior:
b is a static storage duration variable that has zero initialization followed by dynamic initialization. After the zero initialization b's lifetime has begun (under the proposed resolution of CWG 2821). Then reading b during the dynamic initialization would be allowed. b would have value false at this point and initialize b with false again.
Adding constexpr complicates the matter a bit. Now, the initialization must be a constant expression, which would imply that it can't have dynamic initialization, so that the above observation of the zero-initialized state is impossible.
Because the trick with zero-initialization can't work in that case, it can't really work out without undefined behavior. Unfortunately I think that the rules for determining whether the variable's initialization is a constant expression are currently circular for this case and can't give an answer.
Maybe CWG 2186 is relevant: Depending on what "preceding initialization" in [expr.const] means, it may not apply to b when it is used in the initializer. Then the expression can't be a constant expression because the lvalue-to-rvalue conversion on b is not allowed in a constant expression at that point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With