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.
Maybe someone else can do better, but I see only two ways
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
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');
}