I am looking at this https://docs.oracle.com/javase/tutorial/java/generics/subtyping.html and https://docs.oracle.com/javase/tutorial/java/generics/inheritance.html and ask myself how it could be implemented in C++.
I have this small example to illustrate :
#include <iostream>
class Animal
{
public:
virtual std::string type() const = 0;
virtual ~Animal() {}
};
class Dog : public Animal
{
public:
virtual std::string type() const {
return "I am a dog";
}
};
class Cat : public Animal
{
public:
virtual std::string type() const {
return "I am a cat";
}
};
template <typename T>
class AnimalFarm
{
};
void farmTest(const AnimalFarm<Animal *> &farm)
{
std::cout << "test farm";
}
int main(int argc, char *argv[])
{
AnimalFarm<Dog *> dogFarm;
AnimalFarm<Animal *> animalFarm;
farmTest(animalFarm); // OK
farmTest(dogFarm); // NOK compiler error as class AnimalFarm<Dog *> does not inherits from class AnimalFarm<Animal *>
return 0;
}
I understand why it does not work in C++. In Java, the solution is to use the following construction :
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>
(given that Integer
is a subclass of Number
as pointed out in the example links).
Using :
template <typename U>
void farmTest(const AnimalFarm<U *> &farm);
might be solution but is there a better way to do that in C++ without loosing the fact that Cat or Dog inherits from Animal ( as Integer
is a subtype of Number
) ?
Thank you.
If your end game is to form an is-a relationship between AnimalFarm<Dog *>
and AnimalFarm<Animal *>
, then a bit of template specialization with type traits can do that.
template <typename T, typename = void>
class AnimalFarm // Primary template
{
};
template<typename T>
class AnimalFarm<T*,
std::enable_if_t<
!std::is_same<T, Animal>::value &&
std::is_base_of<Animal, T>::value
>
> // Specialization only instantiated when both conditions hold
// Otherwise SFINAE
: public AnimalFarm<Animal*>
{
};
Since AnimalFarm<Animal*>
becomes a public base of AnimalFarm<Dog*>
, the function parameter reference will bind to it. Though you should note that the corresponding hierarchy is flat, no matter how deep the Animal
one goes.
You can check it out live.
Equivalence may be a matter of exactly how we define equivalence (if not being opinion).
If you want a runtime open container for any kind of animal...
Sounds like a good opportunity for runtime polymorphism to solve.
A runtime polymorphic solution seems functionally similar if not equivalent (at least in my thinking) to what you're asking for. The best description I know of for the runtime polymorphism that I mean is Sean Parent's "Better Code: Runtime Polymorphism" talk.
Basically though the idea will hide the Animal
inheritance hierarchy so from the container's perspective you can just use any container like using AnimalFarm = std::vector<Animal>
. The details then go into the implementation of Animal
like so:
class Animal {
struct Concept {
virtual ~Concept() = default;
virtual std::string type() const = 0;
// add in more methods for any other properties your Animal "concept" has
};
template <typename T>
struct Model final: Concept {
Model(T arg): data{std::move(arg)} {}
std::string type() const override {
return get_type_name(data);
}
// override define whatever other methods the concept has
T data; // stores the underlying type's data
};
std::shared_ptr<const Concept> m_self;
public:
template <typename T>
Animal(T v): m_self{std::make_shared<Model<T>>(std::move(v))} {}
// default or delete other constructors as needed.
friend std::string get_type_name(const Animal& animal) {
return animal.m_self->type();
}
};
Now to have an Animal
like a Dog
, you just need free code like:
struct Dog {
};
inline std::string get_type_name(const Dog& dog) {
return "Dog";
}
And usage (assuming at least C++11) is like...
using AnimalFarm = std::vector<Animal>;
int main() {
auto theFarm = AnimalFarm{};
theFarm.push_back(Dog{});
for (const auto& e: theFarm) {
std::cout << get_type_name(e) << "\n";
}
return 0;
}
Note that here, Dog
is instantiated (Dog{}
) and then implicitly gets turned into an Animal
because of the way Animal
's initializing constructor is defined (both templated and non-explicit
).
I just typed this code in by hand. So it's apt to have some mistakes in it. Hopefully it shows the jist of this answer in terms of the code though and I do hope it will help for your need.
If, OTOH, you want the container to only have a specific kind of animal defined by the user of the conatiner...
Then only add instances of the kind of animal to the above styled container.
Or lastly, if you want compiler enforcement...
Then do something like:
template <typename T>
using AnimalFarm = std::vector<T>;
void someCodeBlock() {
AnimalFarm<Dog> dogFarm;
dogFarm.push_back(Dog{});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With