Как достичь "виртуальной функции шаблона" в С++

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

Но в контексте ООП я обнаружил, что приведенный ниже пример не будет очень "естественным", если класс был фактически шаблоном. Обратите внимание, что код фактически не работает, но отчеты gcc-4.3.4: error: templates may not be ‘virtual’

#include <iostream>
#include <vector>

class Animal {
    public:
        template< class AMOUNT >
        virtual void eat( AMOUNT amount ) const { 
            std::cout << "I eat like a generic Animal." << std::endl; 
        }
        virtual ~Animal() { 
        }
};

class Wolf : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a wolf!" << std::endl; 
        }
        virtual ~Wolf() { 
        }
};

class Fish : public Animal {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a fish!" << std::endl; 
        }
        virtual ~Fish() { 
        }
};

class GoldFish : public Fish {
    public:
        template< class AMOUNT >
        void eat( AMOUNT amount) const { 
            std::cout << "I eat like a goldfish!" << std::endl; 
        }
        virtual ~GoldFish() { 
        }
};

class OtherAnimal : public Animal {
        virtual ~OtherAnimal() { 
        }
};

int main() {
    std::vector<Animal*> animals;
    animals.push_back(new Animal());
    animals.push_back(new Wolf());
    animals.push_back(new Fish());
    animals.push_back(new GoldFish());
    animals.push_back(new OtherAnimal());

    for (std::vector<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->eat();
        delete *it;
    }

    return 0;
}

Итак, создание "Fish <Amount> foo" выглядит странно. Тем не менее, мне кажется желательным предоставить произвольное количество пищи для каждого животного.

Таким образом, я ищу решение о том, как достичь чего-то вроде

Fish bar;
bar.eat( SomeAmount food );

Это становится особенно полезным при просмотре цикла for. Можно было бы накормить определенную сумму (FoodAmount) всем разным животным (через eat() и bind1st(), например), это не может быть сделано так легко, хотя я ранен нахожу это очень интуитивным (и, следовательно, в некоторой степени "Естественно". Хотя некоторые могут сейчас возразить, что это связано с "однородным" -характерным вектором вектора, я думаю/хочу, чтобы это было возможно для достижения этого, и я действительно хотел бы знать, как, поскольку это озадачивая меня уже довольно давно...

[EDIT]

Чтобы, возможно, уточнить мотивацию моего вопроса, я хочу запрограммировать класс Exporter и дать от него разные, более специализированные классы. В то время как класс Exporter верхнего уровня, как правило, только для косметического/структурного назначения, выводится класс GraphExporter, который должен снова служить базовым классом для еще более специализированного экспорта. Однако, подобно примеру Animal, я хотел бы иметь возможность определять GraphExporter * даже на специализированных/производных классах (например, на SpecialGraphExplorer), но при вызове "write (out_file)" он должен вызывать соответствующую функцию-член для SpecialGraphExporter of GraphExporter:: write (out_file).

Возможно, это делает мою ситуацию и намерения более ясными.

Бест,

Тень

Ответ 1

После некоторого размышления я понял это как классическое требование multi-method, то есть метод, который отправляет на основе типа времени выполнения более одного параметра. Обычные виртуальные функции сравниваются single dispatch (и они отправляются только по типу this).

Обратитесь к следующему:

  • Андрей Александреску написал (семенные биты для С++?) о реализации мульти-методов с использованием дженериков в "Современном дизайне С++"
    • Глава 11: "Мультиметоды" - он реализует базовые мульти-методы, делая их логарифмическими (используя упорядоченные списки), а затем полностью продвигается к многомерным методам с постоянным временем. Достаточно мощный материал!
  • A статья кодекса, которая, похоже, имеет такую ​​реализацию:
    • не использовать типы отбрасываний любого типа (динамический, статический, реинтерпрет, const или C-стиль)
    • не использовать RTTI;
    • отсутствие препроцессора;
    • безопасность сильного типа;
    • отдельная компиляция;
    • постоянное время выполнения мультиметода;
    • нет динамического распределения памяти (через new или malloc) во время вызова с использованием нескольких методов;
    • отсутствие использования нестандартных библиотек;
    • используются только стандартные функции С++.
  • С++ Open Method Compiler, Питер Пиркельбауэр, Юрий Солодкий и Бьярн Страуструп
  • Библиотека Loki имеет MultipleDispatcher
  • В Википедии есть довольно приятная простая запись с примерами в Multiple Dispatch на С++.

Вот простой подход из статьи wikipedia для справки (менее простой подход масштабируется для большего числа производных типов):

<суб >

// Example using run time type comparison via dynamic_cast

struct Thing {
    virtual void collideWith(Thing& other) = 0;
}

struct Asteroid : Thing {
    void collideWith(Thing& other) {
        // dynamic_cast to a pointer type returns NULL if the cast fails
        // (dynamic_cast to a reference type would throw an exception on failure)
        if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
            // handle Asteroid-Asteroid collision
        } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
            // handle Asteroid-Spaceship collision
        } else {
            // default collision handling here
        }
    }
}

struct Spaceship : Thing {
    void collideWith(Thing& other) {
        if (Asteroid* asteroid = dynamic_cast<Asteroid*>(&other)) {
            // handle Spaceship-Asteroid collision
        } else if (Spaceship* spaceship = dynamic_cast<Spaceship*>(&other)) {
            // handle Spaceship-Spaceship collision
        } else {
            // default collision handling here
        }
    }
}

суб >

Ответ 2

Очевидно, что шаблоны функций виртуальных членов не допускаются и не могут быть реализованы даже теоретически. Чтобы создать виртуальную таблицу базового класса, должно быть конечное число записей виртуальных указателей функций. Шаблон функции допускает неопределенное количество "перегрузок" (т.е. Экземпляров).

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

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

Одно из решений, которое, как мне кажется, служит вашей цели (хотя это трудно понять), элегантно выглядит следующим образом (в основном это шаблон посетителя):

#include <iostream>
#include <vector>

struct Eater { 
  virtual void operator()(int amount) const = 0;
  virtual void operator()(double amount) const = 0;
};

template <typename EaterType>
struct Eater_impl : Eater {
  EaterType& data;
  Eater_impl(EaterType& aData) : data(aData) { };
  virtual void operator()(int amount) const { data.eat_impl(amount); };
  virtual void operator()(double amount) const { data.eat_impl(amount); };
};

class Animal {
  protected:
    Animal(Eater& aEat) : eat(aEat) { };
  public:
    Eater& eat;
    virtual ~Animal() { delete &eat; };
};

class Wolf : public Animal {
  private:
    template< class AMOUNT >
    void eat_impl( AMOUNT amount) const { 
      std::cout << "I eat like a wolf!" << std::endl; 
    }

  public:
    friend struct Eater_impl<Wolf>;        
    Wolf() : Animal(*(new Eater_impl<Wolf>(*this))) { };
    virtual ~Wolf() { };
};

class Fish : public Animal {
  private:
    template< class AMOUNT >
    void eat_impl( AMOUNT amount) const { 
      std::cout << "I eat like a fish!" << std::endl; 
    }
  public:
    friend struct Eater_impl<Fish>;
    Fish() : Animal(*(new Eater_impl<Fish>(*this))) { };
    virtual ~Fish() { };
};

int main() {
  std::vector<Animal*> animals;
  animals.push_back(new Wolf());
  animals.push_back(new Fish());

  for (std::vector<Animal*>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
    (*it)->eat(int(0));
    (*it)->eat(double(0.0));
    delete *it;
  };

  return 0;
};

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

Ответ 3

Я думаю, что шаблон посетителя может быть решением.

UPDATE

Я закончил свой пример:

#include <iostream>
#include <vector>
#include <boost/shared_ptr.hpp>

class Animal;
class Wolf;
class Fish;

class Visitor
{
    public:
    virtual void visit(const Animal& p_animal) const = 0;
    virtual void visit(const Wolf& p_animal) const = 0;
    virtual void visit(const Fish& p_animal) const = 0;
};

template<class AMOUNT>
class AmountVisitor : public Visitor
{
    public:
    AmountVisitor(AMOUNT p_amount) : m_amount(p_amount) {}
    virtual void visit(const Animal& p_animal) const
    {
        std::cout << "I eat like a generic Animal." << std::endl;
    }
    virtual void visit(const Wolf& p_animal) const
    {
        std::cout << "I eat like a wolf!" << std::endl;
    }
    virtual void visit(const Fish& p_animal) const
    {
        std::cout << "I eat like a fish!" << std::endl;
    }


    AMOUNT m_amount;
};

class Animal {
    public:

        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }

        virtual ~Animal() {
        }
};

class Wolf : public Animal {
    public:
        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }
};

class Fish : public Animal {
    public:
        virtual void Accept(const Visitor& p_visitor) const
        {
            p_visitor.visit(*this);
        }
};

int main()
{
    typedef boost::shared_ptr<Animal> TAnimal;
    std::vector<TAnimal> animals;
    animals.push_back(TAnimal(new Animal()));
    animals.push_back(TAnimal(new Wolf()));
    animals.push_back(TAnimal(new Fish()));

    AmountVisitor<int> amount(10);

    for (std::vector<TAnimal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
        (*it)->Accept(amount);
    }

    return 0;
}

это печатает:

I eat like a generic Animal.
I eat like a wolf!
I eat like a fish!

Ответ 4

Запрещена функция виртуального шаблона. Однако вы можете использовать один или другой здесь.

Вы можете создать интерфейс, используя виртуальные методы и реализовать своих различных животных с точки зрения наличия интерфейса для еды. (т.е. PIMPL)

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

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

Ответ 5

Вы можете создать класс шаблона с виртуальной функцией и реализовать функцию в производном классе без использования шаблона следующим образом:

a.h:

template <class T>
class A
{
public:
    A() { qDebug() << "a"; }

    virtual A* Func(T _template) { return new A;}
};


b.h:

class B : public A<int>
{
public:
    B();
    virtual A* Func(int _template) { return new B;}
};


and the function CTOR and call 

  A<int>* a1=new B;
    int x=1;
    a1->Func(x);

К сожалению, я не нашел способ создания виртуальной функции с параметрами шаблона без объявления класса в качестве шаблона и его типа шаблона в классе dervied

Ответ 6

Я скопировал ваш код и изменил его, поэтому теперь он должен работать точно так, как вы хотите:

        #include <iostream>
        #include <vector>

        //defined new enum type
        enum AnimalEnum
        {
           animal,
           wolf,
           fish,
           goldfish,
           other
        };

        //forward declarations
        class Wolf;
        class Fish;
        class GoldFish;
        class OtherAnimal;

        class Animal {
            private:
            AnimalEnum who_really_am_I;
            void* animal_ptr;
            public:
                //declared new constructors overloads for each type of animal
                Animal(const Animal&);
                Animal(const Wolf&);
                Animal(const Fish&);
                Animal(const GoldFish&);
                Animal(const OtherAnimal&);
                template< class AMOUNT >
                /*removed the virtual keyword*/ void eat( AMOUNT amount ) const { 
                    switch (this->who_really_am_I)
                    {
                       case AnimalEnum::other: //You defined OtherAnimal so that it doesn't override the eat action, so it will uses it Animal eat
                       case AnimalEnum::animal: std::cout << "I eat like a generic Animal." << std::endl; break;
                       case AnimalEnum::wolf: ((Wolf*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::fish: ((Fish*)this->animal_ptr)->eat(amount); break;
                       case AnimalEnum::goldfish: ((GoldFish*)this->animal_ptr)->eat(amount) break;
                    }
                }
                void DeleteMemory() { delete this->animal_ptr; }
                virtual ~Animal() { 
                   //there you can choose if whether or not to delete "animal_ptr" here if you want or not
                }
        };

        class Wolf : public Animal {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a wolf!" << std::endl; 
                }
                virtual ~Wolf() { 
                }
        };

        class Fish : public Animal {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a fish!" << std::endl; 
                }
                virtual ~Fish() { 
                }
        };

        class GoldFish : public Fish {
            public:
                template< class AMOUNT >
                void eat( AMOUNT amount) const { 
                    std::cout << "I eat like a goldfish!" << std::endl; 
                }
                virtual ~GoldFish() { 
                }
        };

        class OtherAnimal : public Animal {
                //OtherAnimal constructors must be defined here as Animal constructors
                OtherAnimal(const Animal& a) : Animal(a) {}
                OtherAnimal(const Wolf& w) : Animal(w) {}
                OtherAnimal(const Fish& f) : Animal(f) {}
                OtherAnimal(const GoldFish& g) : Animal(g) {}
                OtherAnimal(const OtherAnimal& o) : Animal(o) {}
                virtual ~OtherAnimal() { 
                }
        };
        //OtherAnimal will be useful only if it has it own actions and members, because if not, typedef Animal OtherAnimal or using OtherAnimal = Animal can be used, and it can be removed from above declarations and below definitions

//Here are the definitions of Animal constructors that were declared above/before:    
        Animal::Animal(const Animal& a) : who_really_am_I(AnimalEnum::animal), animal_ptr(nullptr) {}

        Animal::Animal(const Wolf& w) : who_really_am_I(AnimalEnum::wolf), animal_ptr(new Wolf(w)) {}

        Animal::Animal(const Fish& f) : who_really_am_I(AnimalEnum::fish), animal_ptr(new Fish(f)) {}

        Animal::Animal(const GoldFish& g) : who_really_am_I(AnimalEnum::goldfish), animal_ptr(new GoldFish(g)) {}

        Animal::Animal(const OtherAnimal& o) :
    who_really_am_I(AnimalEnum::other), animal_ptr(new OtherAnimal(o)) {}

        int main() {
            std::vector<Animal> animals;
            animals.push_back(Animal());
            animals.push_back(Wolf()); //Wolf is converted to Animal via constructor
            animals.push_back(Fish()); //Fish is converted to Animal via constructor
            animals.push_back(GoldFish()); //GoldFish is converted to Animal via constructor
            animals.push_back(OtherAnimal()); //OtherAnimal is converted to Animal via constructor

            for (std::vector<Animal>::const_iterator it = animals.begin(); it != animals.end(); ++it) {
                it->eat(); //this is Animal eat that invokes other animals eat
                //delete *it; Now it should be:
                it->DeleteMemory();
            }
            animals.clear(); //All animals have been killed, and we don't want full vector of dead animals.

            return 0;
        }

Ответ 7

В вашем сценарии вы пытаетесь смешивать полиморфизм времени компиляции с полиморфизмом во время выполнения, но это не может быть сделано в этом "направлении".

Существенно, что ваш аргумент шаблона AMOUNT представляет собой ожидаемый интерфейс для типа, реализуемого на основе объединения всех операций, которые каждая из них использует для использования. Если вы хотите создать абстрактный тип, который объявлял бы каждую из этих операций, делая их виртуальными, когда это было необходимо, вы могли бы называть "ешьте" с разными типами (полученными из вашего интерфейса AMOUNT). И он будет вести себя так, как ожидалось.

Ответ 8

В сообщении Mikael я сделал еще один ответ, используя CRTP и следуя Eigen-стилю использования derived() для явного подкласса:

// Adaptation of Visitor Pattern / CRTP from:
// http://stackoverflow.com/a/5872633/170413

#include <iostream>
using std::cout;
using std::endl;

class Base {
public:
  virtual void tpl(int x) = 0;
  virtual void tpl(double x) = 0;
};

// Generics for display
template<typename T>
struct trait {
  static inline const char* name() { return "T"; }
};
template<>
struct trait<int> {
  static inline const char* name() { return "int"; }
};
template<>
struct trait<double> {
  static inline const char* name() { return "double"; }
};

// Use CRTP for dispatch
// Also specify base type to allow for multiple generations
template<typename BaseType, typename DerivedType>
class BaseImpl : public BaseType {
public:
  void tpl(int x) override {
    derived()->tpl_impl(x);
  }
  void tpl(double x) override {
    derived()->tpl_impl(x);
  }
private:
  // Eigen-style
  inline DerivedType* derived() {
    return static_cast<DerivedType*>(this);
  }
  inline const DerivedType* derived() const {
    return static_cast<const DerivedType*>(this);
  }
};

// Have Child extend indirectly from Base
class Child : public BaseImpl<Base, Child> {
protected:
  friend class BaseImpl<Base, Child>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "Child::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};

// Have SubChild extend indirectly from Child
class SubChild : public BaseImpl<Child, SubChild> {
protected:
  friend class BaseImpl<Child, SubChild>;
  template<typename T>
  void tpl_impl(T x) {
    cout << "SubChild::tpl_impl<" << trait<T>::name() << ">(" << x << ")" << endl;
  }
};


template<typename BaseType>
void example(BaseType *p) {
  p->tpl(2);
  p->tpl(3.0);
}

int main() {
  Child c;
  SubChild sc;

  // Polymorphism works for Base as base type
  example<Base>(&c);
  example<Base>(&sc);
  // Polymorphism works for Child as base type
  example<Child>(&sc);
  return 0;
}

Вывод:

Child::tpl_impl<int>(2)
Child::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)
SubChild::tpl_impl<int>(2)
SubChild::tpl_impl<double>(3)

Этот фрагмент можно найти здесь: repro: c808ef0: cpp_quick/virtual_template.cc

Ответ 9

Я не работаю с шаблонами, но думаю:

(1) Вы не можете использовать шаблоны внутри класса, шаблоны больше похожи на глобальные типы или глобальные переменные.

(2) В O.O.P, та же проблема, которую вы представляете, и которую вы пытаетесь решить с помощью шаблонов, может быть решена с помощью наследования.

Классы работают аналогично шаблонам, вы можете расширить их, добавив новые вещи или заменить классы классов указателями, указателями на объекты (A.K.A. "ссылки" ) и переопределить виртуальные функции.

#include <iostream>

struct Animal {
    virtual void eat(int amount ) {
        std::cout << "I eat like a generic Animal." << std::endl;
    }
    virtual ~Animal() { }
};

#if 0

// example 1
struct Wolf : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a wolf!" << std::endl;
    }
};

struct Fish : Animal {
    virtual void eat(int amount) {
        std::cout << "I eat like a fish!" << std::endl;
    }
};

#else

// example 2

struct AnimalFood {
    virtual int readAmount() { return 5; }

    virtual void showName() {
        std::cout << "I'm generic animal food" << std::endl;
    }
};

struct PredatorFood : AnimalFood {
    virtual int readAmount() { return 500; }

    virtual void showName() {
        std::cout << "I'm food for a predator" << std::endl;
    }
};


struct Fish : Animal {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 50) {
            std::cout << "OK food, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "too much food, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Shark : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 250) {
            std::cout << "too litle food for a shark, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

struct Wolf : Fish {
    virtual void eat(AnimalFood* aFood) {
        if (aFood->readAmount() < 150) {
            std::cout << "too litle food for a wolf, Im very hungry, vitamines: " << aFood->readAmount() << std::endl;
        } else {
            std::cout << "OK, vitamines: " << aFood->readAmount() << std::endl;
        }
    }
};

#endif

int main() {
    // find animals
    Wolf* loneWolf = new Wolf();
    Fish* goldenFish = new Fish();
    Shark* sharky = new Shark();

    // prepare food
    AnimalFood* genericFood = new AnimalFood();
    PredatorFood* bigAnimalFood = new PredatorFood();

    // give food to animals
    loneWolf->eat(genericFood);
    loneWolf->eat(bigAnimalFood);

    goldenFish->eat(genericFood);
    goldenFish->eat(bigAnimalFood);

    sharky->eat(genericFood);
    sharky->eat(bigAnimalFood);

    delete bigAnimalFood;
    delete genericFood;

    delete sharky;
    delete goldenFish;
    delete loneWolf;
}

Приветствия.