vukung
vukung

Reputation: 1884

Initialization of a static member inside a template

Here's a minimal example:

#include <iostream>

struct B {
  B() { x = 42; }
  static int x;
};  

int B::x;

template <int N>
struct A {
  int foo() { return b.x; }
  static B b;
};

template<int N>
B A<N>::b;

//template struct A<2>; // explicit instantiation with N = 2 (!)

int main(int argc, char **argv) {
  std::cout << A<1>().foo() << std::endl;
  return 0;
}

This program writes 42 using g++ 4.9.2, but writes 0 using Visual Studio 2015 RC. Also, if I uncomment the explicit instantiation, VS2015RC also gives 42, which is quite interesting, as the template parameter here is different from the one used in the main function.

Is this a bug? I assume that g++ is correct, as there is a reference to b inside foo, so B's constructor should be called.


EDIT: There is a simple workaround - if there is a non-static variable in B, that is referenced in A, VS2015RC will compile correctly:

// ...

struct B {
  B() { x = 42; }
  static int x;
  int y;                         // <- non-static variable
};

// ...

template <int N>
struct A {
  int foo() { b.y; return b.x; } // <- reference to b.y
  static B b;
};

This seems to work, even though b.y, as a statement, is obviously NOP.

Upvotes: 6

Views: 534

Answers (2)

Barry
Barry

Reputation: 302852

From [basic.start.init]:

Variables with static storage duration (3.7.1) or thread storage duration (3.7.2) shall be zero-initialized (8.5) before any other initialization takes place. A constant initializer for an object o is an expression that is a constant expression, except that it may also invoke constexpr constructors for o and its subobjects even if those objects are of non-literal class types. [ ... ]

Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.

In our case, b is statically initialized but b.x is dynamically initialized (the constructor isn't constexpr). But we also have:

It is implementation-defined whether the dynamic initialization of a non-local variable with static storage duration is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first odr-use (3.2) of any function or variable defined in the same translation unit as the variable to be initialized.

Odr-used means, from [basic.def.odr]:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) that does not invoke any nontrivial functions and, if [ ... ]

But evaluating b.x does not yield a constant expression, so we can stop there - b.x is odr-used by A<N>::foo(), which is also the first odr-use. So while the initialization does not have to occur before main(), it does have to occur before foo(). So if you get 0, that's a compiler error.

Upvotes: 6

Laurent Michel
Laurent Michel

Reputation: 405

I would be inclined to write the code like this:

struct B {
   B() {} 
   static int x;
};

int B::x = 42;

After all, the static (x) is defined (and therefore should be initialized) on the last line. Putting the initialization inside the constructor of B means that the static x (there is only one of them!) would be re-initialized each and every single time you construct a B. There is one static, you should initialize only once.

Upvotes: -2

Related Questions