I am new to the forum and this is my very first question...
I've come across what I believe to be an intentional limitation of C++ and would like to know if there is a workaround.
I want to create an abstract base class having a member function that can accept a templatized structure as its parameter, something that according to C++'s rules can't be done for a good reason.
That good reason it is because an abstract base class is intended to provide interface rules to the classes that will derive from it and should not deal with data.
But how would you go about doing something like the following which is probably a reasonable design decision? And if it isn't, let me know...
class Animal {
public:
template <class FOOD, int N>
virtualvoid eat (const FOOD& food) const {
std::cout << "I'm an animal and eat " << N << " times per day." << std::endl;
// Do whatever with food
}
virtual ~Animal() = default;
};
class Wolf: public Animal {
public:
template <class FOOD, int N>
virtualvoid eat (const FOOD& food) const override {
std::cout << "I'm a wolf and eat " << N << " rabbits per day." << std::endl;
// Do whatever with food
}
};
won't compile because template functions cannot be virtual. So a Visitor Pattern solution can solve the problem:
#include <iostream>
#include <vector>
#include <typeinfo>
class Visitor {
public:
virtualvoid visit (class Animal*) const = 0;
virtualvoid visit (class Wolf*) const = 0;
virtualvoid visit (class Fish*) const = 0;
virtualvoid visit (class GoldFish*) const = 0;
};
template <class FOOD, int N>
class FoodVisitor: public Visitor {
private:
FOOD food;
public:
FoodVisitor (const FOOD& _food): food (_food) {}
virtualvoid visit (Animal*) const { // Do whatever with food. For example
if (typeid (food) != typeid (GoldFish))
std::cout << "I'm an animal and eat " << N << " times per day." << std::endl;
else
std::cout << "I even eat goldfish " << N << " times per day." << std::endl;
}
virtualvoid visit (Wolf*) const {
if (typeid (food) != typeid (GoldFish))
std::cout << "I'm a wolf and eat " << N << " rabbits per day." << std::endl;
else
std::cout << "But I don't eat goldfish because they are too tiny." << std::endl;
}
virtualvoid visit (Fish*) const {
if (typeid (food) != typeid (GoldFish))
std::cout << "I'm a fish and eat " << N << " worms per day." << std::endl;
else
std::cout << "I sometimes eat other fish like goldfish " << N << " times per day." << std::endl;
}
virtualvoid visit (GoldFish*) const {
if (typeid (food) != typeid (GoldFish))
std::cout << "I'm a goldfish and eat " << N << " teeny meals per day." << std::endl;
else
std::cout << "But I do not eat my own kind!" << std::endl;
}
};
class Animal {
public:
virtualvoid eat (const Visitor& visitor) { // the "accept" function of the Visitor Pattern
visitor.visit (this);
}
virtual ~Animal() = default;
};
class Wolf: public Animal {
public:
virtualvoid eat (const Visitor& visitor) override { // The template is gone, but a FoodVisitor is a template class that derives from Visitor.
visitor.visit (this);
}
};
class Fish: public Animal {
public:
virtualvoid eat (const Visitor& visitor) override {
visitor.visit (this);
}
};
class GoldFish: public Fish {
public:
virtualvoid eat (const Visitor& visitor) override {
visitor.visit (this);
}
};
int main() {
std::vector<Animal*> animals = {new Animal, new Wolf, new Fish, new GoldFish};
GoldFish goldFish;
FoodVisitor<std::string, 4> foodVisitor1 ("insect");
FoodVisitor<GoldFish, 2> foodVisitor2 (goldFish);
for (Animal* x : animals)
{
x->eat(foodVisitor1); // templates are used, but not for eat but for foodVisitor1
x->eat(foodVisitor2);
}
}
/*
Output:
I'm an animal and eat 4 times per day.
I even eat goldfish 2 times per day.
I'm a wolf and eat 4 rabbits per day.
But I don't eat goldfish because they are too tiny.
I'm a fish and eat 4 worms per day.
I sometimes eat other fish like goldfish 2 times per day.
I'm a goldfish and eat 4 teeny meals per day.
But I do not eat my own kind!
*/
In the above example, FOOD itself can be treated with the Visitor Pattern to treat all the ways that each animal reacts to each type of food.
JLBorges is the person who taught me the Visitor Pattern, by the way.