Search code examples
c++11methodsoverloadingabstract-classvariadic-templates

Generate one method per type from variadic class template


I would like to have a variadic class template to generate one method per type, such that for example a class template like the following:

template <class T, class ... Ts>
class MyClass {
public:
    virtual void hello(const T& t) = 0;
};

would make available the methods hello(const double&) and hello(const int&) when instantiated as MyClass<double, int> myclass;

Note that I want the class to be pure abstract, such that a derived class would actually need to do the implementation, e.g.:

class Derived : MyClass<double, int> {
public:
    inline void hello(const double& t) override { }
    inline void hello(const int& t) override { }
};

This problem is somewhat similar to this one, but I couldn't understand how to adapt it to my case.

EDIT

The recursion inheritance seems to be the right solution for me. How about this more complicated case, where the superclass has more than one method and a template argument is mandatory? Here is what I've tried (but I get error):

template <class MandatoryT, class OptionalT, class... MoreTs>
class MyClass : public MyClass<MandatoryT, MoreTs...> {
public:
    virtual ~MyClass() {}

    virtual char* goodmorning(const MandatoryT& t) = 0;

    virtual bool bye(const MandatoryT& t,
                     const std::map<std::string,bool>& t2) = 0;

  using MyClass<MandatoryT, MoreTs...>::hello;
  virtual void hello(const OptionalT& msg) = 0;
};


template <class MandatoryT, class OptionalT>
class MyClass<MandatoryT, OptionalT> {
  virtual void processSecondaryMessage(const OptionalT& msg) = 0;
};

template <class MandatoryT>
class MyClass<MandatoryT> {
  virtual void processSecondaryMessage() = 0;
}
}

Basically what I want is that the derived class should have one or more types. The first one is used in other methods, while from the second onwards it should be used in hello(). If only one type is provided, an empty hello() is called. But when at least a second type is provided, hello() should use it.

The code above complains that there should be at least two template arguments, because there are "two" ground cases instead of one.


Solution

  • Maybe someone else can do better, but I see only two ways

    1. Recursion inheritance

      You can define MyClass recursively as follows

      // recursive case
      template <typename T, typename ... Ts>
      struct MyClass : public MyClass<Ts...>
       {
         using MyClass<Ts...>::hello;
      
         virtual void hello (const T&) = 0;
       };
      
      // ground case
      template <typename T>
      struct MyClass<T>
       { virtual void hello (const T&) = 0; };
      

    or

    1. variadic inheritance

      You can define another class/struct, say MyHello, that declare a single hello() method, and variadic inherit it from MyClass.

      template <typename T>
      struct MyHello
       { virtual void hello (const T&) = 0; };
      
      template <typename ... Ts>
      struct MyClass : public MyHello<Ts>...
       { };
      

    The recursive example is compatible with type collision (that is: works also when a type is present more time in the list of template arguments MyClass; by example MyClass<int, double, int>).

    The variadic inheritance case, unfortunately, isn't.

    The following is a full compiling example

    #if 1
    // recursive case
    template <typename T, typename ... Ts>
    struct MyClass : public MyClass<Ts...>
     {
       using MyClass<Ts...>::hello;
    
       virtual void hello (const T&) = 0;
     };
    
    // ground case
    template <typename T>
    struct MyClass<T>
     { virtual void hello (const T&) = 0; };
    #else
    
    template <typename T>
    struct MyHello
     { virtual void hello (const T&) = 0; };
    
    template <typename ... Ts>
    struct MyClass : public MyHello<Ts>...
     { };
    
    #endif
    
    struct Derived : public MyClass<double, int>
     {
       inline void hello (const double&) override { }
       inline void hello (const int&) override { }
     };
    
    int main()
     {
       Derived d;
    
       d.hello(1.0);
       d.hello(2);
     }
    

    -- EDIT --

    The OP asks

    how about a more complicated case where MyClass has more than one method and I always need to have one template argument (see edited question)?

    From your question I don't understand what do you exactly want.

    But supposing you want a pure virtual method, say goodmorning() that receive a MandT (the mandatory type), a pure virtual method hello() for every type following MandT or an hello() without arguments when the list after MandT is empty.

    A possible solution is the following

    // declaration and groundcase with only mandatory type (other cases
    // intecepted by specializations)
    template <typename MandT, typename ...>
    struct MyClass
     {
       virtual void hello () = 0;
    
       virtual ~MyClass () {}
    
       virtual char * goodmorning (MandT const &) = 0;
     };
    
    // groundcase with a single optional type
    template <typename MandT, typename OptT>
    struct MyClass<MandT, OptT>
     {
       virtual void hello (OptT const &) = 0;
    
       virtual ~MyClass () {}
    
       virtual char * goodmorning (MandT const &) = 0;
     };
    
    // recursive case
    template <typename MandT, typename OptT, typename ... MoreOptTs>
    struct MyClass<MandT, OptT, MoreOptTs...>
       : public MyClass<MandT, MoreOptTs...>
     {
       using MyClass<MandT, MoreOptTs...>::hello;
    
       virtual void hello (OptT const &) = 0;
    
       virtual ~MyClass () {}
     };
    

    Here the recursion is a little more complicated than before.

    In case you instantiate a MyClass with only the mandatory type (by example: MyClass<char>) the main version ("groundcase with only mandatory type") is selected because the two specialization doesn't match (no first optional type).

    In case you instantiate a Myclass with one optional type (say MyClass<char, double>) the specialization "groundcase with a single optional type" is selected because is the most specialized version.

    In case you instantiate a MyClass with two or more optional type (say MyClass<char, double, int> start recursion (last specialization) until remain an single optional type (so the "groundcase with a single optional type" is selected).

    Observe that I've placed the goodmorning() in both ground cases, because you don't need to define it recursively.

    The following is a full compiling example

    // declaration and groundcase with only mandatory type (other cases
    // intecepted by specializations)
    template <typename MandT, typename ...>
    struct MyClass
     {
       virtual void hello () = 0;
    
       virtual ~MyClass () {}
    
       virtual char * goodmorning (MandT const &) = 0;
     };
    
    // groundcase with a single optional type
    template <typename MandT, typename OptT>
    struct MyClass<MandT, OptT>
     {
       virtual void hello (OptT const &) = 0;
    
       virtual ~MyClass () {}
    
       virtual char * goodmorning (MandT const &) = 0;
     };
    
    // recursive case
    template <typename MandT, typename OptT, typename ... MoreOptTs>
    struct MyClass<MandT, OptT, MoreOptTs...>
       : public MyClass<MandT, MoreOptTs...>
     {
       using MyClass<MandT, MoreOptTs...>::hello;
    
       virtual void hello (OptT const &) = 0;
    
       virtual ~MyClass () {}
     };
    
    
    struct Derived0 : public MyClass<char>
     {
       void hello () override { }
    
       char * goodmorning (char const &) override
        { return nullptr; }
     };
    struct Derived1 : public MyClass<char, double>
     {
       void hello (double const &) override { }
    
       char * goodmorning (char const &) override
        { return nullptr; }
     };
    
    struct Derived2 : public MyClass<char, double, int>
     {
       void hello (double const &) override { }
       void hello (int const &) override { }
    
       char * goodmorning (char const &) override
        { return nullptr; }
     };
    
    
    
    int main()
     {
       Derived0  d0;
       Derived1  d1;
       Derived2  d2;
    
       d0.hello();
       d0.goodmorning('a');
    
       d1.hello(1.2);
       d1.goodmorning('b');
    
       d2.hello(3.4);
       d2.hello(5);
       d2.goodmorning('c');
     }