Генерация одного метода для каждого типа из шаблона класса Variadic. - PullRequest
3 голосов
/ 11 июля 2019

Я хотел бы иметь шаблон класса variadic для генерации одного метода для каждого типа, такого как, например, шаблон класса, подобный следующему:

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

сделает доступными методы hello(const double&) и hello(const int&), когда будет создан экземпляр MyClass<double, int> myclass;

Обратите внимание, что я хочу, чтобы класс был чисто абстрактным, чтобы производный класс действительно нуждался в реализации, например ::

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

Эта проблема чем-то похожа на эту , но я не мог понять, как адаптировать ее к моему делу.

EDIT

Мне кажется, рекурсивное наследование - правильное решение для меня. Как насчет этого более сложного случая, когда суперкласс имеет более одного метода и аргумент шаблона является обязательным? Вот что я пробовал (но я получаю ошибку):

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;
}
}

По сути, я хочу, чтобы производный класс имел один или несколько типов. Первый используется в других методах, а со второго он должен использоваться в hello(). Если указан только один тип, вызывается пустой hello(). Но если указан хотя бы второй тип, hello() должен использовать его.

Приведенный выше код жалуется, что должно быть не менее двух аргументов шаблона, потому что вместо одного есть «два» основных случая.

1 Ответ

4 голосов
/ 11 июля 2019

Может быть, кто-то еще может сделать лучше, но я вижу только два пути

  1. Наследование рекурсии

    Вы можете определить 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; };
    

или

  1. вариадное наследование

    Вы можете определить другой класс / структуру, скажем, 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');
 }
...