3

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.

max66
  • 65,235
  • 10
  • 71
  • 111
mcamurri
  • 153
  • 11

1 Answers1

4

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');
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • The second version with variadic inheritance also has the benefit that `MyHello` can be used as an interface. – aschepler Jul 11 '19 at 19:08
  • @aschepler - yes... I'm trying to make it work but I have some compiler errors (I'm a disaster with virtual inheritance). – max66 Jul 11 '19 at 19:10
  • I don't see how virtual inheritance would help here. – aschepler Jul 11 '19 at 19:12
  • @aschepler - sorry... I've misunderstood your "variadic" with "virtual"; the problem I'm trying to solve is the case of type collisions in `Ts...` list; for recursive version isn't a problem but with virtual inheritance there is collision; I'm trying with virtual inheritance but doesn't works. – max66 Jul 11 '19 at 19:14
  • You mean if a type appears twice, like `MyClass`? – aschepler Jul 11 '19 at 19:16
  • @aschepler - exactly. – max66 Jul 11 '19 at 19:17
  • I'd probably just document that's invalid. Or if there's reason to support it, I'd attempt to work in something that filters out duplicates to determine the actual base class list. – aschepler Jul 11 '19 at 19:22
  • The variadic inheritance version has one additional slight problem. [It doesn't work at all](https://ideone.com/p7pPYE). – n. m. could be an AI Jul 12 '19 at 05:54
  • @n.m. - I didn't know at all this problem that, if I understand correctly, is the same of [this question](https://stackoverflow.com/questions/51690394/overloading-member-function-among-multiple-base-classes). In your code can be easily solved adding `using MyHello::hello...;` in `MyClass` but, unfortunately, variadic `using` is available only starting from C++17 (if I remember correctly). Anyway, taking in count that (for this question) the it's necessary override the functions in `MyClass`, this shouldn't be a problem (in this case). – max66 Jul 12 '19 at 08:19
  • Overriding it in the derived class solves the ambiguity for the derived class. When you have a virtual fcn you normally want to call it via a base class pointer/ref. I didn't know about variadic using, it is neat. – n. m. could be an AI Jul 12 '19 at 08:47
  • Thank you @max66, I haven't completely understood your solution because in order to understand recursion you first need to understand recursion...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)? – mcamurri Jul 12 '19 at 09:31
  • @MarcoCamurri - Yes... recursion is a little complicated sometime. Anyway, I've improved the answer trying to respond to your edit (with a more complicated recursion example; sorry). – max66 Jul 12 '19 at 12:24
  • @max66 you are a genius and you win. – mcamurri Jul 12 '19 at 14:46