Search code examples
c++11templatesspecializationvariadic

Not understanding C++ Template Specialization And Constants


I am trying to wrap my head around template specialization, and I am a bit confused (perhaps not understanding what typename actually is, or what the compiler expects)

Example 1 (Compiles):

template <typename A, typename... Args>
class Example
{
public:
    Example(){}
    virtual ~Example(){}
};

template <typename A, typename... Args>
class Example<A, int, Args...>{
    Example(){}
    virtual ~Example(){}
};

Example 2 (Compiles):

template <typename A, int, typename... Args>
class Example
{
public:
    Example(){}
    virtual ~Example(){}
};

template <typename A, typename... Args>
class Example<A, 2, Args...>{
    Example(){}
    virtual ~Example(){}
};

Example 3 (Fails):

template <typename A, typename... Args>
class Example
{
public:
    Example(){}
    virtual ~Example(){}
};

template <typename A, typename... Args>
class Example<A, 2, Args...>{
    Example(){}
    virtual ~Example(){}
};

The error is:

error: type/value mismatch at argument 2 in template parameter list for ‘template class Example

Questions:

First, I am new to generic programming, and I hope I am asking the right questions. The compiler spec terminology is still a bit foreign to me.

  • What is happening? Is the compiler trying to treat the constant as typename?
  • If typename can be specialized as int, and int can be specialized as 2, why can't a typename be specialized as 2?
  • What would be a "proper" method to specialize the class with int or enum?
  • Am I asking the right questions?

Thank you

EDIT/Solution:

After I understood what is going on (from Yakk's explanation), here is how my final solution looks like. I read somewhere by one of the C++ guru's that "You can solve any problem by adding another layer of abstraction". Now I know what that means :D

 enum ETypes
{
    UNKNOWN = 0,
    INT = 1,
    FLOAT = 2,
    STRING = 3,
    FUNC = 4,
};

// This is to use the ETypes as a type.
// Note that T is not a type, hence use it as RHS
template<ETypes T>
class ETypeName
{
public:
    ETypes type = T;
};


// The example
template <typename A, typename... Args>
class Example
{
private:
    Example();              // Hide the constructor as private
                            // to generate compilation error
    virtual ~Example(){}
};


// LOOK! We can use the Enum to specialize the class.
template <>
class Example<ETypeName<ETypes::INT>>{
public:
    ETypes mType;
    Example():mType(ETypes::INT){}
    virtual ~Example(){}
};

And in main():

    Example<ETypeName<ETypes::INT>> x;

    // This can't happen. Private constructor. Not specialized yet
//  Example<ETypeName<ETypes::FLOAT>> x1;

Solution

  • A primary specialization looks like this:

    template <typename A, typename... Args>
    class Example
    

    When you type Example<stuff goes here>, it is always matched against the primary specialization's <typename A, typename... Args> argument list.

    This is a completely different beast:

    template <typename A, typename... Args>
    class Example<A, int, Args...>
    

    This is a secondary specialization. Here,

    template <typename A, typename... Args>
    

    is not the argument list, but rather the deduction list.

    The argument list is:

    class Example<A, int, Args...>
    

    here. What is between the <> is only used to pattern match against arguments passed to the primary specialization.

    Types and non-type template parameters are different things. The primary specialization details what arguments are type, and what arguments are non-type.

    Once they have matched against the primary, each of the secondary specializations are pattern matched against the arguments. Each viable candidate is examined, and a reasonably complex system is used to determine which is "more specialized", the rules of which I won't go into here.