C++ виртуальная шаблонная функция(псевдо)

300
31 марта 2018, 16:52

Итак, создать такую функцию нельзя, знаю. Однако мне очень требуется. В связи с этим интересуюсь, есть ли какие-нибудь обходные примеры? Пока кроме такого ничего не придумал:

class A
{
protected:
    virtual void _func(const char*) = 0; // метод для класса B
    virtual void _func(float) = 0; // метод для класса C
public:
    template<class T> void func(T e) { this->_func(e); }
};
class B : public A
{
private:
    void _func(const char* i) {};
protected:
    void _func(float i) { std::cout << i << std::endl; };
};
class C : public A
{
private:
    void _func(float i) { std::cout << "тест" << std::endl; };
protected:
    void _func(const char* i) { std::cout << i << std::endl; };
};
int main()
{
    B b;
    b.func(14.4); // вызовем _func(float) из B
    C c;
    c.func("Привет"); // вызовем _func(int) из C
    c.func(14.9);
    return 0;
}

Однако недостатки очевидны: приходиться переопределять ненужную виртуальную функцию в каждом классе, которой пользоваться не придется. При этом, можно каждому дочернему классу передать аргумент, который мы не хотим передавать(показано в методе void _func(float i) { std::cout << "тест" << std::endl; }; ) Есть ли более элегантные решения? Спасибо.

Answer 1

Я не очень понял, чего вы хотите добиться в примере, но если говорить о виртуальных шаблонных функциях - она не может быть виртуальной, однако шаблонный класс может иметь виртуальную функцию. На определенном этапе инстанцирования шаблонов типы, которые можно передавать в шаблонную псевдовиртуальную функцию становятся известными (иначе бы и смысла не было), а значит ничего не мешает создать кортеж указателей на такие шаблонные классы с обычными виртуальными функциями, конкретизированные нужными вам типами.

template<class T>
class Visitor{
   virtual void visit(T& value);
};
template<class... Ts>
class Storage{
     std::tuple<Visitor<Ts>*...> m_visitors;
     public:
     template<std::size_t I, class T>
     void visit(T& value){
        auto visitor = std::get<I>(m_visitors);
        visitor->visit(value);
     }
};

Тут возникает проблема с инициализацией визитёров - на каждый тип нужен свой экземпляр, но можно решить проблему при помощи обобщенных лямбд.

В Storage::visit I и T на самом деле связаны, но без метопрограммирования одно из другого получить не получится.

Другой вариант - передавать в качестве аргумента виртуальной функции std::variant с допустимыми типами, а уже реализация виртуальной функции решит, что делать с конкретными типами. Если типы-аргументы имеют разный размер, в std::variant можно передавать ссылки, а не значения (оборачивая их в std::reference_wrapper), или можно использовать boost::variant, который поддерживает обычные ссылки.

template<class... Ts>
class Storage{
  std::tuple<Ts...> m_values;
  virtual void visit(boost::variant<Ts&...> ref){}
public:
  Storage(std::tuple<Ts...> values): m_values(values) {}
  void callVisit(){
      std::apply([this](auto&... v) mutable{
        auto visit = [this](auto& v){ this->visit(v); return 0;};
        int unused[] = {visit(v)...}; // Вызов this->visit для всех значений кортежа
      }, m_values);
  }
};
template<class... Ts>
class StorageImpl: public Storage<Ts...>{
   void visit(boost::variant<Ts&...> ref) override {
       boost::apply_visitor([](auto& value){
          // тут по сути тело шаблонной виртуальной функции, value - шаблонный аргумент
       } ,ref);
   }
public:
   using Storage<Ts...>::Storage;
};

Ничего не мешает передавать два и более аргумента при таком подходе, главное - так или иначе узнать типы, допустимые для передачи.

Answer 2

Ваш дизайн сейчас примерно соответствует такому: абстрактный класс Животное, в который вы одновременно запихиваете функции Летать и Плавать, и далее вы хотите из него порождать, например, Орла и Дельфина. Но это неверный подход. Где, в каком месте у вас "Животное вообще" должно летать? или плавать? Да, есть какие-нибудь летучие рыбы или там утки - но если вы поступаете так, то ограничиваете понятие животных только теми, кто может летать и плавать одновременно.

Если у вас будет код, который вы хотите - с запретом летать в производном классе, например - то к чему приведет ваш (вполне корректный) вызов через указатель на базовый абстрактный класс типа Животное->Летать при передаче ему какого-то Тигра? К ошибке времени выполнения.

В самом лучшем случае нужно дополнить иерархию абстрактными классами, скажем, Птицы и Водоплавающие, а уже потом на них строить конкретные классы. (Кстати, тогда летающие рыбы будут получаться множественным наследованием.) Не надо идти против природы.

Если вы решите свою проблему, запрещая передачу не того аргумента - вы получите новую проблему - обработку ошибки передачи неверного аргумента во время работы программы, в то время как при правильной иерархии вылавливанием такой ошибки может заняться компилятор.

Если и это непонятно - умываю руки, делайте квадратное колесо, а потом спрашивайте, как написать трактор, который будет способен тащить телегу с такими колесами...

READ ALSO
C++ паттерн &ldquo;стратегия&rdquo;

C++ паттерн “стратегия”

Попробовал, перепеписать код из книжки Head First про паттерны, на C++, но появляется ошибка E0322 object of abstract class type "MallardDuck" is not allowed: Duck d:\Code\CODE\C++\Duck\Duck\Sourcecpp...

247
указатель на массив символов

указатель на массив символов

В чем разница char arr[100] и char *arr1 = new char[100] и почему функция gets_s(char*) работает при gets_s(arr), но не работает gets_s(arr1)?

239
Не могу решить задачу по программе с++

Не могу решить задачу по программе с++

Найдите такое число x, что x^2+sqrt(x)=C , с точностью не менее 6 знаков после точки

227