views:

960

answers:

3

Hello all,
For static member initialization I use a nested helper struct, which works fine for non templated classes. However, if the enclosing class is parameterized by a template, the nested initialization class is not instantiated, if the helper object is not accessed in the main code. For illustration, a simplified example (In my case, I need to initialize a vector).

#include <string>
#include <iostream>

struct A
{
    struct InitHelper
    {
        InitHelper()
        {
            A::mA = "Hello, I'm A.";
        }
    };
    static std::string mA;
    static InitHelper mInit;

    static const std::string& getA(){ return mA; }
};
std::string A::mA;
A::InitHelper A::mInit;


template<class T>
struct B
{
    struct InitHelper
    {
        InitHelper()
        {
            B<T>::mB = "Hello, I'm B."; // [3]
        }
    };
    static std::string mB;
    static InitHelper mInit;

    static const std::string& getB() { return mB; }
    static InitHelper& getHelper(){ return mInit; }
};
template<class T>
std::string B<T>::mB; //[4]
template<class T>
typename B<T>::InitHelper B<T>::mInit;


int main(int argc, char* argv[])
{
    std::cout << "A = " << A::getA() << std::endl;

//    std::cout << "B = " << B<int>::getB() << std::endl; // [1]
//    B<int>::getHelper();    // [2]
}

With g++ 4.4.1:

  • [1] and [2] commented:

    A = Hello, I'm A.

    Works as intended

  • [1] uncommented:

    A = Hello, I'm A.
    B = 

    I would expect, that the InitHelper initializes mB

  • [1] and [2] uncommented:
    A = Hello, I'm A.
    B = Hello, I'm B.

    Works as intended
  • [1] commented, [2] uncommented:
    Segfault in the static initialization stage at [3]

Thus my question: Is this a compiler bug or is the bug sitting between the monitor and the chair? And if the latter is the case: Is there an elegant solution (i.e. without explicitly calling a static initialization method)?

Thank you very much for any suggestions/comments.

Update I:
This seems to be a desired behavior (as defined in the ISO/IEC C++ 2003 standard, 14.7.1):

Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.

+2  A: 
  • [1] uncommented case: It is ok. static InitHelper B<int>::mInit does not exist. If member of the template class (struct) is not used it does not compiled.

  • [1] and [2] uncommented case: It is ok. B<int>::getHelper() use static InitHelper B<int>::mInit and mInit exists.

  • [1] commented, [2] uncommented: it works for me in VS2008.

Alexey Malistov
Hmm, ok. Thank you for investigating this.
Mr. Mr.
+3  A: 

The problem is that the defintions you give for the static member variables are templates too.

template<class T>
std::string B<T>::mB;
template<class T>
typename B<T>::InitHelper B<T>::mInit;

During compilation, this defines actually nothing, since T is not known. It is something like a class declaration or a template definition, the compiler does not generate code or reserve storage when it sees it.

The definition happens implicitly later, when you use the template class. Because in the segfaulting case you don't use B<int>::mInit, it is never created.

A solution would be explictly defining the needed member (without initializing it): Put somewhere source file a

template<>
typename B<int>::InitHelper B<int>::mInit;

This works basically the same way as explictly defining a template class.

drhirsch
I think, in this very example, it would not make a difference to put the initialization into another header file as everything is in the same translation unit.
Mr. Mr.
Of course. Thats why I used the conditional tense ("would"). But I am not a native english speaker, so this is proably misleading. I deleted it.
drhirsch
So do you understand now why you get a segfault/empty member, or is it still unclear?
drhirsch
Yes, thank you. I read it up in the C++ standard (14.7.1):> Unless a member of a class template or a member template has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist; in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist.
Mr. Mr.
Just for clarification: In case 3 / the segfaulting case: The segfault does not occur, because mInit does not exists, but because mB does not exist. I think this is because there is not guaranteed order in static object initialization.
Mr. Mr.
In the segfaulting case, he uses `B<int>::getHelper()`, which uses `B<int>::mInit`.
Johannes Schaub - litb
+5  A: 

This was discussed on usenet some time ago, while i was trying to answer another question on stackoverflow: Point of Instantiation of Static Data Members. I think it's worth reducing the test-case, and considering each scenario in isolation, so let's look at it more general first:


struct C { C(int n) { printf("%d\n", n); } };

template<int N>
struct A {
  static C c;
}; 

template<int N>
C A<N>::c(N); 

A<1> a; // implicit instantiation of A<1> and 2
A<2> b;

You have the definition of a static data member template. This does not yet create any data members, because of 14.7.1:

"... in particular, the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist."

The definition of something (= entity) is needed when that entity is "used", according to the one definition rule which defines that word (at 3.2/2). In particular, if all references are from uninstantiated templates, members of a template or a sizeof expressions or similar things that don't "use" the entity (since they are either not potentially evaluating it, or they just don't exist yet as functions/member functions that are itself used), such a static data member is not instantiated.

An implicit instantiation by 14.7.1/7 instantiates declarations of static data members - that is to say, it will instantiate any template needed to process that declaration. It won't, however, instantiate definitions - that is to say, initializers are not instantiated and constructors of the type of that static data member are not implicitly defined (marked as used).

That all means, the above code will output nothing yet. Let's cause implicit instantiations of the static data members now.

int main() { 
  A<1>::c; // reference them
  A<2>::c; 
}

This will cause the two static data members to exist, but the question is - how is the order of initialization? On a simple read, one might think that 3.6.2/1 applies, which says (emphasis by me):

"Objects with static storage duration defined in namespace scope in the same translation unit and dynamically initialized shall be initialized in the order in which their definition appears in the translation unit."

Now as said in the usenet post and explained in this defect report, these static data members are not defined in a translation unit, but they are instantiated in a instantiation unit, as explained at 2.1/1:

Each translated translation unit is examined to produce a list of required instantiations. [Note: this may include instantiations which have been explicitly requested (14.7.2). ] The definitions of the required templates are located. It is implementation-defined whether the source of the translation units containing these definitions is required to be available. [Note: an implementation could encode sufficient information into the translated translation unit so as to ensure the source is not required here. ] All the required instantiations are performed to produce instantiation units. [Note: these are similar to translated translation units, but contain no references to uninstantiated templates and no template definitions. ] The program is ill-formed if any instantiation fails.

The Point of Instantiation of such a member also does not really matter, because such a point of instantiation is the context link between an instantiation and its translation units - it defines the declarations that are visible (as specified at 14.6.4.1, and each of those point of instantiations must give instantiations the same meaning, as specified in the one definition rule at 3.2/5, last bullet).

If we want ordered initialization, we have to arrange so we don't mess with instantiations, but with explicit declarations - this is the area of explicit specializations, as these are not really different to normal declarations. In fact, C++0x changed its wording of 3.6.2 to the following:

Dynamic initialization of a non-local object with static storage duration is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization.


This means to your code, that:

  • [1] and [2] commented: No reference to the static data members exist, so their definitions (and also not their declarations, since there is no need for instantiation of B<int>) are not instantiated. No side effect occurs.
  • [1] uncommented: B<int>::getB() is used, which in itself uses B<int>::mB, which requires that static member to exist. The string is initialized prior to main (at any case before that statement, as part of initializing non-local objects). Nothing uses B<int>::mInit, so it's not instantiated, and so no object of B<int>::InitHelper is ever created, which makes its constructor not being used, which in turn will never assign something to B<int>::mB: You will just output an empty string.
  • [1] and [2] uncommented: That this worked for you is luck (or the opposite :)). There is no requirement for a particular order of initialization calls, as explained above. It might work on VC++, fail on GCC and work on clang. We don't know.
  • [1] commented, [2] uncommented: Same problem - again, both static data members are used: B<int>::mInit is used by B<int>::getHelper, and the instantiation of B<int>::mInit will cause its constructor to be instantiated, which will use B<int>::mB - but for your compiler, the order is different in this particular run (unspecified behavior is not required to be consistent among different runs): It initializes B<int>::mInit first, which will operate on a not-yet-constructed string object.
Johannes Schaub - litb
Thank you for your excellent comment and very clear and elaborate discussion of this problem.
Mr. Mr.