Может быть, кто-то еще может сделать лучше, но я вижу только два пути
Наследование рекурсии
Вы можете определить MyClass
рекурсивно следующим образом
// 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; };
или
вариадное наследование
Вы можете определить другой класс / структуру, скажем, MyHello
, которая объявляет
один hello()
метод, и variadic наследуют его от MyClass
.
template <typename T>
struct MyHello
{ virtual void hello (const T&) = 0; };
template <typename ... Ts>
struct MyClass : public MyHello<Ts>...
{ };
Рекурсивный пример совместим с коллизиями типов (то есть: работает также, когда тип присутствует больше времени в списке аргументов шаблона MyClass
; на примере MyClass<int, double, int>
).
Дело о вариативном наследовании, к сожалению, нет.
Ниже приведен полный пример компиляции
#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);
}
- РЕДАКТИРОВАТЬ -
ОП просит
как насчет более сложного случая, когда MyClass имеет более одного метода, и мне всегда нужно иметь один аргумент шаблона (см. Отредактированный вопрос)?
Из твоего вопроса я не понимаю, чего именно ты хочешь.
Но предположим, что вам нужен чисто виртуальный метод, скажем goodmorning()
, который получает MandT
(обязательный тип), чисто виртуальный метод hello()
для каждого типа, следующего за MandT
или hello()
без аргументов, когда список после MandT
пуст.
Возможное решение:
// 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 () {}
};
Здесь рекурсия немного сложнее, чем раньше.
В случае, если вы создаете экземпляр MyClass
только с обязательным типом (например: MyClass<char>
), выбирается основная версия («базовый сценарий только с обязательным типом»), потому что две специализации не совпадают (первая необязательная) типа).
В случае, если вы создаете экземпляр Myclass
с одним необязательным типом (скажем, MyClass<char, double>
), выбирается специализация "Groundcase с одним необязательным типом", потому что это наиболее специализированная версия.
В случае, если вы создаете экземпляр MyClass
с двумя или более необязательными типами (скажем, MyClass<char, double, int>
запускать рекурсию (последнюю специализацию), пока не останется один необязательный тип (поэтому выбирается «базовый элемент с одним необязательным типом»).
Обратите внимание, что я поместил goodmorning()
в обоих случаях, потому что вам не нужно определять его рекурсивно.
Ниже приведен полный пример компиляции
// 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');
}