Home > Blockchain >  Reading values of static const data members without definitions: what governs these rules?
Reading values of static const data members without definitions: what governs these rules?

Time:03-11

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 and S::a as ill-formed, whilst it accepts read the values of S::e (empty) and S::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.

  • Related