1

Please take a look at the following:

#include <string>
#include <unordered_map>

template <int N> class Object;
template <int N> class Thing;

template <int N>
class Factory {
    private:
        using FuncPtr = Object<N>*(*)(Thing<N>*);
        static std::unordered_map<std::string, FuncPtr> map;
    public:
        static void insertInMap (const std::string& tag, FuncPtr funcPtr) {
            map.emplace (tag, funcPtr);
        }
};
template <int N> 
std::unordered_map<std::string, typename Factory<N>::FuncPtr> Factory<N>::map;

// won't compile on GCC 4.8.1:
//template <> std::unordered_map<std::string, typename Factory<0>::FuncPtr> Factory<0>::map;  

template <int N> struct Object {};

struct Blob : Object<0> {
    static Blob prototype;
    Blob() {Factory<0>::insertInMap ("Blob", Blob::create);}
    Blob (Thing<0>*) {/* */}
    static Object<0>* create (Thing<0>* x) {return new Blob(x);}
};
Blob Blob::prototype;  // Calls up Factory<0>::insertInMap during compile time, but crashes when run.

int main()
{
}

So it appears that Blob Blob::prototype; crashes because Factory<0>::map has not been instantiated yet, so I try to instantiate it with the line:

template <> std::unordered_map<std::string, typename Factory<0>::FuncPtr> Factory<0>::map;

but it won't compile (with GCC 4.8.1):

C:\Users\Andy\AppData\Local\Temp\ccsGlFeV.o:Practice.cpp:(.text$_ZN7FactoryILi0E
E11insertInMapERKSsPFP6ObjectILi0EEP5ThingILi0EEE[__ZN7FactoryILi0EE11insertInMa
pERKSsPFP6ObjectILi0EEP5ThingILi0EEE]+0x14): undefined reference to `Factory<0>:
:map'
collect2.exe: error: ld returned 1 exit status
Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
prestokeys
  • 4,817
  • 3
  • 20
  • 43

1 Answers1

2

Instead of specializing Factory<N>::map for <0>, just explicitly instantiate the entire class:

template class Factory<0>;

in place of //template <> ...

DEMO


UPDATE

For Visual Studio, which seems to still fail to initialize the static field even the template is explicitly instantiated before first usage, you can alternatively specialize the entire class:

template <>
class Factory<0> {
    private:
        typedef Object<0>*(*FuncPtr)(Thing<0>*);
        static std::unordered_map<std::string, FuncPtr> map;
    public:
        static void insertInMap (const std::string& tag, FuncPtr funcPtr) {
            map.emplace (tag, funcPtr);
    }
};
std::unordered_map<std::string, Factory<0>::FuncPtr> Factory<0>::map;

or define the field for Factory<0> (though I don't know why VS accepts that and does not trigger an error, as the syntax is not valid):

std::unordered_map<std::string, Factory<0>::FuncPtr> Factory<0>::map;

DEMO 2

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Yes, this solution works with GCC. But when I run it on Visual Studio 2013, it still crashes because of reading nullptr. Which compiler is bugged here? Is there a solution that works on both? Because I check my compiling with both compilers. – prestokeys Sep 13 '14 at 17:03
  • Static order initialization fiasco only happens on static variables in different compilation units – Marco A. Sep 13 '14 at 17:28
  • Ok, I will specialize all the different Factory classes individually, like I originally did, and try to use private inheritance to capture the common stuff. VS2013 actually accepts my original code above (with the commented-out line used), but I knew the code is wrong anyway. Thanks. – prestokeys Sep 13 '14 at 17:30