Search code examples
c++templatesc++11enumstemplate-templates

Tagging objects using enums via template-template parameters


I would like to use an enum argument of a template, to restrict a second argument, a class, to in turn taking an member of the enum as an argument as it's templated parameter. In code, I would expect this to look like:

CObject<EObjectTag, CSubObject<EObjectTag::CAT_A>> cObject;

this should work, however:

CObject<EObjectTag, CSubObject<ENotAnObjectTag::CAT_OTHER>> cObject;

should fail as ENotAnObjectTag::CAT_OTHER is not a element of EObjectTag.

My implementation (attempt) of this, is as follows and bombs out during compilation (on gcc version 4.9.2 (Ubuntu 4.9.2-10ubuntu13)) with the error message:

source.cc:16:45: error: ‘SUBOBJECT_TAG’ was not declared in this scope struct CObject>

#include <iostream>
#include <typeinfo>

enum class EObjectTag {CAT_A, CAT_B, CAT_OTHER};

// CSubObject
template<class OBJECT_TAG_T, OBJECT_TAG_T OBJECT_TAG>
struct CSubObject { OBJECT_TAG_T m_tTag = OBJECT_TAG; };

// CObject - Forward declaration
template <class SUBOBJECT_TAG_T, template <SUBOBJECT_TAG_T SUBOBJECT_TAG> class SUBOBJECT_T>
struct CObject;

// CObject - Specialization
template <class SUBOBJECT_TAG_T, template <SUBOBJECT_TAG_T SUBOBJECT_TAG> class SUBOBJECT_T>
struct CObject<SUBOBJECT_T<SUBOBJECT_TAG_T, SUBOBJECT_TAG>>
{
   public:
      SUBOBJECT_T<SUBOBJECT_TAG_T, SUBOBJECT_TAG> m_cSubObject;
};

int main() {
   // The aim is that the second object only accepts a tag that 
   // belongs to EObjectTag
   CObject<EObjectTag, CSubObject<EObjectTag::CAT_A>> cObject;

   return 0;
}

The final use case for this involves replacing CSubObject with CObject so that we can use recursion to define a hierarchy of tagged objects, which also requires the use of variadic templates to have multiple objects at the same level. For example:

/* EBase, */
CObject</*EBase::BASE,*/ EObject,
    CObject<EObject::INIT, EInitObject,
        CObject<EInitObject::INIT_FOO>,
        CObject<EInitObject::INIT_BAR>,
    >,
    CObject<EObject::COUNT, ECountObject,
        CObject<ECountObject::COUNT_FOO>,
        CObject<ECountObject::COUNT_BAR>,
    >,
> cMyObjectHierarchy;

The commented out references to EBase (an enum internal to the library) are there to keep the template parameters of CObject consistent, I would plan (if possible) to do this automatically via template specialization or default arguments.

My goals of specifying this hierarchy of objects would in addition include:

  1. Avoid forcing the user of this library to define additional classes or structs in their program
  2. Leverage compile time checking via the templating of CObject with an enum, whose functions in turn use that enum as an argument to a set of functions common to all CObjects

Solution

  • I made some changes to make it compile. Although I'm not 100% sure if this actually does what you want it to do; I agree with most of what Yakk sais in his answer.

    Note: the following will not compile because I deliberately tried to mix a type of one enum with a value of another enum to verify that it indeed triggers a compile-time error, which I think is sort of what you asked for.

    #include <iostream>
    #include <typeinfo>
    
    enum class EObjectTag {CAT_A, CAT_B, CAT_OTHER};
    enum class FObjectTag {DOG_A, DOG_B, DOG_OTHER};
    
    // CSubObject
    template<typename OBJECT_TAG_T, OBJECT_TAG_T OBJECT_TAG>
    struct CSubObject { OBJECT_TAG_T m_tTag = OBJECT_TAG; };
    
    // CObject - Specialization
    template <class SUBOBJECT_TAG_T, SUBOBJECT_TAG_T SUBOBJECT_TAG, template <typename TYPE_T, TYPE_T TYPE> class SUBOBJECT_T>
    struct CObject
    {
       public:
          SUBOBJECT_T<SUBOBJECT_TAG_T, SUBOBJECT_TAG> m_cSubObject;
    };
    
    int main() {
       // The aim is that the second object only accepts a tag that 
       // belongs to EObjectTag
       CObject<EObjectTag, EObjectTag::CAT_A, CSubObject> cObject1;
       CObject<EObjectTag, FObjectTag::DOG_B, CSubObject> cObject2;
    
       return 0;
    }