Search code examples
c++templatesargumentsenum-class

How to include enum class as class template arguments?


I'm trying to write a generic container class that I can use as a library. I want to use it to instantiate each object in a collection exactly once. So I thought to pass in both the pointer to the new instance and an accomodating enum class to be able to make sure all objects are unique and accounted for.

After repeating myself a few times in functions like these ...

enum class HBeams
{
    HEB100,
    enumSize
};

void HBeam::CreatePredefinedHBeam(std::map<HBeams, HBeam *> & _library, HBeams _key, ushort _width, ushort _height)
{
    if(_library.count(_key) == 0)
    {
        _library.insert(std::pair<HBeams, HBeam *>(_key, new HBeam{ _width, _height } ));
        if(_library.count(_key) == 1)
        {
            Logger::Log(Status::OK, "Created predefined HBeam with ID ", static_cast<int>(_key));
            return;
        }
    }

    Logger::Log(Status::Warning, "Failed to create predefined HBeam with ID ", static_cast<int>(_key));
    return;
}

... I felt I should create a generic container class for this. However,

// Library.h
template <class T, enum class E>
class Library
{
    std::map<E, T*>  m_entries;

public:
    void  Insert(T* _entry, E _enum);
};

// Library.cpp
template<class T, enum class E>
void Library<T, E>::Insert(T* _entry, E _enum)
{
  ...
}

... first tells me I must use 'enum' not 'enum class', then that my template argument for non-type template parameter should be an expression. It seems I can overcome this by defining all enum classes in the header of the Library class like this:

enum class HBeams
{
    HEB100,
    enumSize
};

template <class T>
class HBeamLibrary
{
    std::map<HBeams, T*>  m_entries;

public:
    void  Insert(T* _entry, HBeams _key);
};

... but then I still have to manually create a class for each implementation. I there a way to make the "enum class template" work?


Solution

  • You can do a simple condition over the second template argument:

    template <class T, class E, class = typename std::enable_if<std::is_enum<E>::value>::type>
    class Library
    {
        std::map<E, T*>  m_entries;
    
    public:
        void Insert(T* _entry, E _enum) {
            if(m_entries.count(_enum) == 0)
            {
                m_entries.insert(std::pair<E, T*>(_enum, _entry));
                if(m_entries.count(_enum) == 1)
                {
                    return;
                }
            }
        }
    };
    

    Explanation:

    E - can be both class or enum.
    The third class parameter - Only legal if e is an enum.

    Example:

    enum class A {
        A,
        B
    };
    class Test {};
    Library<Test, A> l; // The code will compile
    Test t;
    l.Insert(&t, A::A); // Works just fine
    
    // ----------------
    
    Library<Test, Test> l; // The code won't compile
    // Error: "template argument 3 is invalid"
    

    How does it work?

    enable_if declaration looks like this:

    /// Define a member typedef @c type only if a boolean constant is true.
    template<bool, typename _Tp = void>
    struct enable_if { }; // no type named `type` is declared as class sub type
    
    // Partial specialization for true.
    template<typename _Tp>
    struct enable_if<true, _Tp> { // if the condition is true
        typedef _Tp type; // type named `type` is declared as class sub type
    };
    

    enable_if gives you the opportunity to to check a condition in the compilation time. If the condition set to true, then the partial specialization for true will be invoke, which contains the sub type named type. In this case the third template argument will receive a legal and existing type, and everything will work just fine.

    The interesting case is when enable_if condition set to false. In this case, this struct won't contain any sub type named type, and any try to access this type member like:

    std::enable_if<false::value>::type
    

    Will invoke a compilation error.