How can we allow template class specified by enum as template argument to have a common function?

Viewed 40

I'm trying to distinguish class implementation based on enum as a template argument. But, I want the class to have common function as below. The reason is that the common function behaves independent of the enum value. The code cannot be compiled because I'm doing something wrong. How can we resolve this?

#include <iostream>                                                                                    

enum E{
  E1, 
  E2  
};

template <enum E>
class X { 
  public:
  void f_common() {
   std::cout << "common" << std::endl;
  }
};

template <>
class X<E::E1> {
  public:
  void f() {
   std::cout << "E1" << std::endl;
  }
};

template <>
class X<E::E2> {
  public:
  void f() {
   std::cout << "E2" << std::endl;
  }
};


int main() {
  X<E::E1> e1; 
  X<E::E2> e2; 

  e1.f(); // "E1" is shown
  e2.f(); // "E2" is shown

  e1.f_common();// compile error
  e2.f_common();// compile error

  return 0;
}

2 Answers

In short: Template specialization will not "inherit" anything from template, because you will totally "overwrite" the implementation of template by specializing it.

Long answer: It seems like you have confused template specialization with inheritance. If you want to really use a class template you need to instantiate it, by doing so (implicitly or explicitly) you will create a group of brand new classes. Unlike class inheritance, there are no relationship among those classes, as a result, for example, if you wrote a function that accepts X<E::E1> as a parameter, then compiler will reject to call it with an X<E::E2> object.

Template specialization is used to provide special implementations to some specific template parameters. By giving a template specialization, you asked the compiler to use the special implementation, instead of instantiating a new class from generic implementation. Therefore both X<E::E1> and X<E::E2> do not contain f_common() method, because the declaration of f_common does not appear in the specialized implementations.

The different specializations are unrelated to each other

There are several ways to fix your code (without repeat common code):

  • use base class

    class X_common { 
    public:
      void f_common() const { std::cout << "common" << std::endl; }
    };
    
    template <E> class X;
    
    template <>
    class X<E::E1> : X_common {
    public:
      void f() const { std::cout << "E1" << std::endl; }
    };
    template <>
    class X<E::E2> : X_common {
    public:
      void f() const { std::cout << "E2" << std::endl; }
    };
    
  • fully specialize method

    template <E> class X
    { 
    public:
        void f_common() const { std::cout << "common" << std::endl; }
        void f() const;
    };
    
    template <>
    void X<E::E1>::f() const { std::cout << "E1" << std::endl; }
    template <>
    void X<E::E2>::f() const { std::cout << "E2" << std::endl; }
    
  • in C++17, if constexpr (your example works with regular if)

    template <E e> class X
    { 
    public:
        void f_common() const { std::cout << "common" << std::endl; }
        void f() const
        {
            if constexpr (e == E::E1) {
                std::cout << "E1" << std::endl;
            } else {
                std::cout << "E2" << std::endl;
            }
        }
    };
    
  • requires in C++20:

    template <E e> class X
    { 
    public:
        void f_common() const { std::cout << "common" << std::endl; }
        void f() const requires(e == E::E1) { std::cout << "E1" << std::endl;}
        void f() const requires(e == E::E2) { std::cout << "E2" << std::endl;}
    };
    
Related