Search code examples
c++templatestemplate-specializationtype-traits

Partial Template Specialization using enable_if


I am trying to understand how to use type traits with std::enable_if to "enable" partial specializations of a class. Here is the example code I am attempting to get workingL

#include <type_traits>
#include <iostream>

class AbstractFoo {
public:
    virtual const char* name() const = 0;
};

template <typename T, typename Enable = void>
class Foo;

template <>
class Foo<int> : public AbstractFoo{
public:
    const char* name() const override { return "int"; }
};

template <>
class Foo<char> : public AbstractFoo {
public:
    const char* name() const override { return "char"; }
};

template <typename T>
class Foo<T, typename std::enable_if<std::is_enum<T>::value, T>::type> : public AbstractFoo {
public:
    const char* name() const override { return "enum"; }
};

enum class MyEnum {
    VAL1,
    VAL2,
    VAL3
};

int main() {
    Foo<int> v1;
    Foo<char> v2;
    Foo<MyEnum> v3;
    
    std::cout << "v1.name()=" << v1.name() << std::endl;
    std::cout << "v2.name()=" << v2.name() << std::endl;
    std::cout << "v3.name()=" << v3.name() << std::endl;

    return 0;
};

My goal is to have specific specializations for certain types (e.g., int and char), but, if the type is an enum, to use my partial specialization. However, when I attempt to compile this code, I get the following error

error: aggregate 'Foo v3' has incomplete type and cannot be defined

Which I assume means my specialization was not chosen and thus never becomes defined. What am I doing wrong?


Solution

  • In the declaration of Foo the second template parameter defaults to void. That means that the following variable:

    Foo<MyEnum> v3;
    

    is actually

    Foo<MyEnum, void> v3;
    

    Now the question is: does this correspond to the specialization you want? Not really, because in your specialization for enum:

    • std::is_enum<T>::value = true when T = MyEnum
    • std::enable_if<std::is_enum<T>::value, T>::type = T = MyEnum when T=MyEnum

    So, for T=MyEnum, your give the specialization for Foo<T, T>, which does not match the variable above.

    As noted in a comment, a simple fix is to declare the specialization as

    class Foo<T, typename std::enable_if<std::is_enum<T>::value>::type>
    

    In this way, the second template parameter is void when T=MyEnum, so it matches the variable declaration of v3.