Search code examples
c++templatesenable-if

C++ enable_if class specialization not invoked


I have the following example of trying to use enable_if for instantiating 2 different specializations of the class A, but I do not understand it correctly as they are not getting invoked, any suggestions ?

#include <type_traits>
#include <iostream>


class SC1 {
public:
  SC1() {
    std::cout << "SC1\n";
  }
};

class SC2 {
 public:
  SC2() {
    std::cout << "SC2\n";
  }

  void f() {};
};

template <class T, typename TC1 = void, class TC2 = void>
class A  {
public :
  A() {
    std::cout << "default A\n";
  };
};


template <class T>
class A <T, typename std::enable_if_t<T::SC1_ENABLE, typename T::EN>, void> : public  T::EN {
  static_assert(!std::is_same<typename T::EN, SC1>::value);
public:
    A() {
      std::cout << "1 type is " << typeid(typename std::enable_if_t<T::SC1_ENABLE>).name() << "\n";
  }
};


template <typename T>
class A <T, void,  typename std::enable_if_t<T::SC2_ENABLE,  typename T::EN>> : public T::EN {
public:
  A() {
    static_assert(std::is_same<typename T::EN, SC2>::value);    
    //std::cout << "2 type is " << typeid(typename std::enable_if_t<T::SC2_ENABLE>).name() << "\n";
  }
  
};

  
  struct T1 {
    static constexpr bool SC1_ENABLE = false;
    static constexpr bool SC2_ENABLE = false;
  };

  struct T2 {
    static constexpr bool SC1_ENABLE = true;
    static constexpr bool SC2_ENABLE = false;
    typedef SC1 EN;
  };

  struct T3 {
    static constexpr bool SC2_ENABLE = true;
    static constexpr bool SC1_ENABLE = false;
    typedef SC2 EN;
  };

int main() {
  //    std::enable_if_t<T3::SC2_ENABLE,T3::EN>();

  A<T3>();
  A<T2>();

}

I have tried different variants of it, compiled it with c++ std 20 using GCC and only the default implementation of A gets invoked.


Solution

  • You're using std::enable_if wrong.

    In the primary template, you're using typename TC1 = void for the template parameter (which could be simplified to typename = void by the way). For A<T2>() (equivalent to A<T2, void, void>()) to use the first partial specialization, the partial specialization must also have void there.

    However, you are using typename std::enable_if_t<T::SC1_ENABLE, typename T::EN>, which is the type T::EN after substitution, so this will never match. It works once you write:

    std::enable_if_t<T::SC1_ENABLE>
    

    Note that void is the default argument for the second template parameter, and typename is unnecessary.

    However, this would be dropping the requirement that T::EN has to be a type, so you could write something like:

    template <class T>
    class A <T, std::enable_if_t<T::SC1_ENABLE, std::void_t<typename T::EN>, void>
      : public T::EN { /* ... */ };
    

    Also keep in mind that having two separate voids like that for different requirements does not impose some ordering between them, and this will likely blow up in your face. See Reliable way of ordering mulitple std::void_t partial specializations for type traits for reliable solutions.

    C++20 Solution

    If you're compiling with C++20, you should also use C++20 features; namely, concepts and constraints:

    template <typename T>
    concept T2_like = T::SC1_ENABLE && requires { typename T::EN; };
    
    template <class T> // primary template
    struct A {};
    
    template <T2_like T> // partial specialization with type-constraint for T
    struct A<T> {};