FlintZA
FlintZA

Reputation: 872

Initializer for static member on template not always called in statically linked library

I have a template class intended to help with enum string conversion. It's based on this solution to automatically initializing static variables. The full class is as follows:

template <typename LabelType>
class EnumHelper
{
public:

    static const char* ToString(LabelType label)
    {
        return enumBimap_.left.at(label);
    }

    static LabelType ToEnum(const char* name)
    {
        return enumBimap_.right.at(name);
    }

    static bool IsInitialized() { return initialized_; }

private:
    typedef boost::bimap<LabelType, const char*> EnumBimap;

    EnumHelper() = delete;
    EnumHelper(const EnumHelper&) = delete;
    static void Init(int numLabels, const char* names[])
    {
        for (int i = 0; i < numLabels; i++)
        {
            enumBimap_.insert(EnumBimap::value_type((LabelType)i, names[i]));
        }

        initialized_ = true;
    }

    class Initializer
    {
    public:
        Initializer();
    };

    static EnumBimap enumBimap_;
    static bool initialized_;
    static Initializer initializer_;

    friend class Initializer;
};

template <typename LabelType>
typename EnumHelper<LabelType>::EnumBimap EnumHelper<LabelType>::enumBimap_;

template <typename LabelType>
bool EnumHelper<LabelType>::initialized_ = false;

template <typename LabelType>
typename EnumHelper<LabelType>::Initializer EnumHelper<LabelType>::initializer_;

The macro for initializing it specializes the Init method for the particular enum, and instantiates the template for that enum:

#define INIT_ENUM_HELPER(labelType, labelNames, count) \
template <> \
EnumHelper<labelType>::Initializer::Initializer() \
{ \
    EnumHelper<labelType>::Init(count, labelNames); \
} \
template class EnumHelper<labelType>;

Now I'm using this successfully in two cases in a library statically linked to my application, but in another case the Init never gets called. The initialization is exactly the same (calling the macro in a .cpp in the library) I've run through a couple of possibilities for it not working, including that it doesn't get referenced in the library itself, and that the enum was initially internal to a class (grasping at straws) but I really am at a bit of a loss now.

Under what conditions would that static Initalizer object not be constructed, hence not calling the Init?

Upvotes: 0

Views: 69

Answers (1)

FlintZA
FlintZA

Reputation: 872

Not an answer to getting this data to initialize automatically across libraries, but an alternative implementation that doesn't rely on automatic static initialization and is lazy initialized as needed:

    // Helper for easily obtaining string representations of enum values and vice versa
    template <typename LabelType>
    class EnumHelper
    {
    public:
        typedef boost::bimap<LabelType, std::string > EnumBimap;

        static const char* ToString(LabelType label)
        {
            auto const& left = Instance().enumBimap_.left;
            auto iter = left.find(label);
            if (iter != left.end())
            {
                return iter->second.c_str();
            }
            else
            {
                return "UNDEFINED_ENUM_VALUE";
            }
        }

        static LabelType ToEnum(const char* name)
        {
            auto const& right = Instance().enumBimap_.right;
            std::string str(name);
            auto iter = right.find(str);
            CHECK_THROW(iter != right.end(), ERROR_MISSING_VALUE, "ENUM");

            return iter->second;
        }

        static bool IsInitialized() { return true;  }

    private:
        EnumHelper() = delete;
        EnumHelper(const EnumHelper&) = delete;
        EnumHelper(int numLabels, const char* names[])
        {
            for (int i = 0; i < numLabels; i++)
            {
                std::string str(names[i]);
                enumBimap_.insert(EnumBimap::value_type((LabelType)i, str));
            }
        }
        static const EnumHelper& Instance();

        EnumBimap enumBimap_;
    };

    // Sets up a specific enum helper by having its instance accessor init with appropriate label names
    #define INIT_ENUM_HELPER(labelType, labelNames, count) \
        template <> \
        const EnumHelper<labelType>& EnumHelper<labelType>::Instance() \
        { \
            static EnumHelper<labelType> enumHelper(count, labelNames); \
            return enumHelper; \
        }

This is easily set up for a particular enum:

// In .h
enum class GameStateLabels
{
    LOADING,
    INTRO,
    PLAY,
    RESULTS,
    COUNT
};
const char* GAME_STATE_LABEL_NAMES[];

// In .cpp
const char* VSE::GAME_STATE_LABEL_NAMES[] =
{
    "LOADING",
    "INTRO",
    "PLAY",
    "RESULTS"
};

INIT_ENUM_HELPER(GameStateLabels, GAME_STATE_LABEL_NAMES, (int)GameStateLabels::COUNT)

If anyone has an actual solution for the original issue, I'm happy to try it and accept it if it works :)

Upvotes: 0

Related Questions