Consider the following program:
struct Empty { };
struct NonEmpty { int x{}; };
struct S {
static const Empty e; // declaration
static const NonEmpty n; // declaration
static const int a; // declaration
static const int b{}; // declaration and initializer
};
Empty ge; // declaration & definition
NonEmpty gn; // declaration & definition
int ga; // declaration & definition
int main() {
auto le1 = S::e; // OK
auto ln1 = S::n; // #1 error: undefined reference
auto la1 = S::a; // #2 error: undefined reference
auto lb1 = S::b; // OK
auto le2 = ::ge; // OK
auto ln2 = ::gn; // OK
auto la2 = ::ga; // OK
}
None of the static data members of S
are defined, but of the two integral type static data members S::a
and S::b
the latter specifies a brace-or-equal-initializer at its in-class declaration, as it may, as per [class.static.data]/4. S::e
and S::n
may not specify brace-or-equal-initializer:s at their in-class declaration.
All these static data members of S
have, like their global namespace-scope counterpart variables ge
, gn
and ga
, static storage duration. As per [basic.start.static]/2 they all thus undergo zero initialization as part of static initialization.
The program above is, however, rejected by GCC and Clang (various language versions, various compiler versions) at #1
and #2
, when trying to copy-initialize local variables from the S::n
and S::a
static data members.
I'm assuming the compilers are correct, but I cannot find the normative section which supports this rejection, or maybe rather, why the program accept the case with the empty class static data member S::e
. That the case of S::b
is accepted "makes sense", but again I can't sort this out normatively (odr-use, conv.lval, basic.life, ... ?).
Question
- What normative text explains why the program above rejects reading the value of
S::e
andS::a
as ill-formed, whilst it accepts read the values ofS::e
(empty) andS::b
(initialized)? Say, in N4868.
CodePudding user response:
[basic.def.odr]/4 governs when a variable is odr-used (requiring a definition).
S::e
and S::n
are not eligible for exception (4.1) because they have non-reference type. They are not eligible for exception (4.2) because they are not usable in constant expressions because they fail to be potentially-constant, and they do not meet the "non-volatile-qualified non-class type to which the lvalue-to-rvalue conversion is applied" criterion either. They are not eligible for exception (4.3) because they are not discarded either; they are bound to the reference parameters of the copy constructors.
This implies that S::e
and S::n
are odr-used. You only got a diagnostic for S::n
, not S::e
. This does not imply that your use of S::e
was acceptable. The implementation is not required to diagnose a violation of the ODR. The compilers you have used have probably elided the call to the (trivial) copy constructor of Empty
, and generated object files where the linker has no way to know that S::e
was supposed to have been defined.
S::b
falls under exception (4.2) because it is usable in constant expressions, and the lvalue-to-rvalue conversion is immediately applied (i.e., the expression referring to S::b
is "converted" into the value of S::b
immediately). It is not odr-used, and does not need a definition.
S::a
does not fall under exception (4.2) because its initializing declaration is not reachable, which has made it not usable in constant expressions. It is odr-used, and needs a definition.