Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initializing a static constexpr data member of the base class by using a static constexpr data member of the derived class

Consider the following code:

template<typename T>
struct S { static constexpr int bar = T::foo; };

struct U: S<U> { static constexpr int foo = 42; };

int main() { }

GCC v6.1 compiles it, clang 3.8 rejects it with the error:

2 : error: no member named 'foo' in 'U'
struct S { static constexpr int bar = T::foo; };

Which compiler is right?
Could it be due to the fact that U is not a complete type at the point where we try to use it within S?
In this case, it should be considered a bug of GCC, but I'd like to know if I'm right before to search/open an issue on the bug tracker...

EDIT

Meanwhile I've opened a bug to GCC.
Waiting for it to accept the answer.

like image 426
skypjack Avatar asked Jun 14 '16 15:06

skypjack


People also ask

How do you initialize a static member in C++?

We can put static members (Functions or Variables) in C++ classes. For the static variables, we have to initialize them after defining the class. To initialize we have to use the class name then scope resolution operator (::), then the variable name. Now we can assign some value.

Should I use static with constexpr?

A static constexpr variable has to be set at compilation, because its lifetime is the the whole program. Without the static keyword, the compiler isn't bound to set the value at compilation, and could decide to set it later.

Is constexpr implicitly static?

constexpr is a compile time feature, where inline/static are runtime features. The meaning of constexpr is more restricted than static. The meaning is that given a particular input value the return value is always the same, and the copiler ought to be able to work it out completely during compilation.

What can be constexpr?

constexpr indicates that the value, or return value, is constant and, where possible, is computed at compile time. A constexpr integral value can be used wherever a const integer is required, such as in template arguments and array declarations.


1 Answers

For C++14 and 11, Clang is right; however, things have changed in the latest working draft (the future C++17) - see the next section.

The Standard quotes to look for are (from N4140, the draft closest to C++14):

[temp.inst]/1:

[...] The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions, member classes, scoped member enumerations, static data members and member templates; [...]

[temp.point]/4:

For a class template specialization, [...] the point of instantiation for such a specialization immediately precedes the namespace scope declaration or definition that refers to the specialization.

So, the point of instantiation for S<U> is right before the declaration of U, with only a forward declaration struct U; conceptually inserted before, so that the name U is found.

[class.static.data]/3:

[...] A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [...] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

According to the paragraph quoted above, the declaration of bar within the definition of S, even though it has an initializer, is still just a declaration, not a definition, so it's instantiated when S<U> is implicitly instantiated, and there's no U::foo at that time.

A workaround is to make bar a function; according to the first quote, the function's definition will not be instantiated at the time of the implicit instantiation of S<U>. As long as you use bar after the definition of U has been seen (or from within the bodies of other member functions of S, since those, in turn, will only be instantiated separately when needed - [14.6.4.1p1]), something like this will work:

template<class T> struct S 
{
   static constexpr int bar() { return T::foo; }
};

struct U : S<U> { static constexpr int foo = 42; };

int main()
{
   constexpr int b = U::bar();
   static_assert(b == 42, "oops");
}

Following the adoption of P0386R2 into the working draft (currently N4606), [class.static.data]/3 has been amended; the relevant part now reads:

[...] An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.1). [...]

This is complemented by the change to [basic.def]/2.3:

A declaration is a definition unless:
[...]

  • it declares a non-inline static data member in a class definition (9.2, 9.2.3),

[...]

So, if it's inline, it's a definition (with or without an initializer). And [dcl.constexpr]/1 says:

[...] A function or static data member declared with the constexpr specifier is implicitly an inline function or variable (7.1.6). [...]

Which means the declaration of bar is now a definition, and according to the quotes in the previous section it's not instantiated for the implicit instantiation of S<U>; only a declaration of bar, which doesn't include the initializer, is instantiated at that time.

The changes in this case are nicely summarized in the example in [depr.static_constexpr] in the current working draft:

struct A {
   static constexpr int n = 5; // definition (declaration in C++ 2014)
};

const int A::n; // redundant declaration (definition in C++ 2014)

This makes GCC's behaviour standard-conformant in C++1z mode.

like image 132
bogdan Avatar answered Sep 27 '22 15:09

bogdan