3

I would like to have a templated class with a static data member, and initialize it by emulating a "static constructor." For a non-templated class, this has already been answered (see static constructors in C++? I need to initialize private static objects and What is a static constructor?). However, none of the answers seem to work for a templated class.

The following is an example that tries to adapt the "static constructor" idiom from the previous answers to a templated class. (Note that the example is simply initializing an int and could be written without such constructors; however, I require a general solution.)

#include <iostream>

struct Foo
{
    static int x;
    static struct init
    {
        init()
        {
            std::cout << "Initializing Foo..." << std::endl;
            x = 1;
        }
    } initializer;
};
int Foo::x;
Foo::init Foo::initializer;

template<int N>
struct Bar
{
    static int x;
    static struct init
    {
        init()
        {
            std::cout << "Initializing Bar..." << std::endl;
            x = N;
        }
    } initializer;
};

template<int N>
int Bar<N>::x;
template<int N>
typename Bar<N>::init Bar<N>::initializer;

int main()
{
    std::cout << Foo::x << std::endl;
    std::cout << Bar<1>::x << std::endl;
    return 0;
}

This outputs:

Initializing Foo...
1
0

But I expected it to output:

Initializing Foo...
Initializing Bar...
1
1

Is this an example of the "static initialization order fiasco?"

  • _"Is this an example of the "static initialization order fiasco?""_ Sounds quite so. Just avoid `static` globals and `Singletons` (unless these are really Singletons). – user0042 Dec 07 '17 at 21:28
  • @user0042 only it is not. – SergeyA Dec 07 '17 at 21:28
  • @SergeyA By means all these template instantiation are completely independent it's not, you're correct. I would avoid doing that though. – user0042 Dec 07 '17 at 21:32
  • @user0042 not sure what you mean. The problem OP describes has **nothing** to do with the order of static initialization. – SergeyA Dec 07 '17 at 21:38

3 Answers3

2

No, it is not static initialization order fiasco. It is simply a result of the fact that every member of a template class is a template on it's own, and as such is not instantiated until used.

Your code never uses init member, so init is never instantiated.

However, your problem is easily solved:

#include <iostream>

template<int N>
struct Bar 
{
    static int x;
};

template<int N>
int Bar<N>::x= N;

int main()
{
    std::cout << Bar<1>::x << std::endl;
    return 0;
}

This gives you what you want in a simpler way.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • I need a general constructor solution, not simply a solution that works for `int`s. As stated in my original post: "Note that the example is simply initializing an int and could be written without such constructors; however, I require a general solution." – Dan Fortunato Dec 07 '17 at 21:39
  • @DanFortunato, doesn't matter. You can initialize your value this way to anything you want. – SergeyA Dec 07 '17 at 21:52
  • Using `=` is not general enough. Suppose my static data member is something more complex, such as an array or other data structure, and I would like to initialize its elements according to some arbitrary function. – Dan Fortunato Dec 07 '17 at 22:00
  • @DanFortunato, update your question with your MCVE, and I hope to be able to answer your specific question. – SergeyA Dec 08 '17 at 14:18
2

You need to explictly instantiate initializer:

[...]
template<int N>
typename Bar<N>::init Bar<N>::initializer;

template
typename Bar<1>::init Bar<1>::initializer;

int main()
{
    std::cout << Foo::x << std::endl;
    std::cout << Bar<1>::x << std::endl;
    return 0;
}

The reason is that Bar<1>::x does not depends on Bar<1>::initializer. So the compiler does not instantiate it as you do not use it. Actualy, initializer initialization does not initialize x. x is first zero initialized, then if initializer is instantiated, x is assigned a new value.

There are no risk of static initialization fiasco as long as initializer is instantiated in the same translation unit as the one where x is instantiated. So it is certainly a good idea to explictly instantiate x too.


Alternatively you could declare these variables as static locals:

#include <iostream>
template<int N>
struct Bar
{
    static int x()
      {
      static int x_val;
      static struct init
        {
        init()
          {
          std::cout << "Initializing Bar..." << std::endl;
          x_val = N;
          }
        } initializer;//indeed this circumvolution is no more needed.
      return x_val;
      }
};
int main(){
    std::cout << Bar<1>::x() << std::endl;
}

But if the initialization is not trivial, the generated code inside x() may be under optimized.


Depending on your problem, you could also define x as a wrapper around an int:

class int_inited{
  int val;
  public:
  int_inited(){
    std::cout << "Perfoming initialization" << std::endl;
    val=42;
    }
  operator int&(){
    return val;
    }
  operator const int &() const{
    return val;
    }
  };

template<class N>
struct Bar{
  static int_inited x;
  [...];

Oliv
  • 17,610
  • 1
  • 29
  • 72
  • I see. I was under the impression that `template typename Bar::init Bar::initializer;` was doing the instantiation. If I need to explicitly instantiate `Bar<1>::initializer`---a constructor that I would like to be called automatically---doesn't that defeat the purpose of it? Do you have any way to make this happen automatically? – Dan Fortunato Dec 07 '17 at 22:05
  • 1
    @DanFortunato I have added an alternative, personaly I dislike the technic I use in this alternative, the generated assembly can be ugly, each time you execute `x()` there may appear some code that test if `x_val` or `initializer` are already initialized!! – Oliv Dec 07 '17 at 22:20
  • Is the alternative the closest way to achieve what I want, or is there something better? – Dan Fortunato Dec 07 '17 at 23:01
  • Actualy, from a phylosophical point of view, I can not know exactly what you want. I can not pretend either this is the best solution, this is the best I have found. Maybe if you are sure that `x()` will be called for the first time on only one thread, you could define your function as a trampoline. – Oliv Dec 08 '17 at 09:06
  • The `int_inited` solution is what I currently do, but I was hoping for something more elegant than wrapping the data in a class. – Dan Fortunato Dec 08 '17 at 21:34
  • Is beauty accessible from a language that much formalized? – Oliv Dec 08 '17 at 23:06
  • Just a doubt, you know you can initialize a static variable, template or not, using a function. So maybe you should reconsider this option. – Oliv Dec 09 '17 at 09:36
0

I have found a clean solution that works for any data type. Since the assignment operation inside a template is evaluated when the compiler comes across a specific Bar<N>::x to instantiate, we can write:

template<int N>
int Bar<N>::x = init<N>();

where init() is a function templated on N that returns an int. Additionally, init() will only be called once for each value of N that the compiler instantiates.

As a more useful example, here I initialize a static std::array according to some arbitrary function:

#include <iostream>
#include <array>

template<int N>
struct Foo
{
    static std::array<double,N> x;
};

template<int N>
std::array<double,N> init()
{
    std::array<double,N> y;
    for (int i=0; i<N; ++i) {
        y[i] = (double)(i*i+i)/N;
    }
    return y;
}

template<int N>
std::array<double,N> Foo<N>::x = init<N>();

int main()
{
    const int N = 10;
    for (int i=0; i<N; ++i) {
        std::cout << Foo<N>::x[i] << std::endl;
    }
    return 0;
}