Java Generics are not the same kind of thing as C++ templates.
C++ values of class type are not the same thing as Java reference variables of class type.
You are running into both problems here.
C++ templates generate a new, unrelated type for each set of template arguments. You can create a common base, but you have to do it yourself.
Java Generics, under the hood, actually create a single class. It then writes casting operations at inputs and outputs.
So a Java Generic, Foo<Base>
and Foo<Derived>
are related, because the the Java Generic actually creates a Foo<Object>
then wraps it up in casts, and those casts in Foo<Base>
and Foo<Derived>
are compatible. (well, not always Object
, you mark up the generic arguments with information that Java uses to determine what the actual type it writes its Generic for, but that gives you the idea).
In C++, there is no relation. (well, template pattern matching gives you a compile-time relation, but no runtime relation at all)
The second problem is that you are treating values of class type like references. In C++, a Foo
is an actual foo. It represents a block of memory that is an instance of that class. In Java, a Foo
is a smart pointer to an object on the heap somewhere that obeys the Foo
protocol (is a derived class).
You cannot easily make a value of type Foo
in Java, and you cannot easily make a mark and sweep smart pointer to a Foo
in C++.
Foo<Bar> fooBar;
Foo<Baz> fooBaz;
these are two unrelated types. They are stored on the stack (automatic storage).
std::vector<Foo<Bar>> v;
This stores a buffer of memory containing Foo<Bar>
objects packed together.
v.push_back(fooBar);
This copies a fooBar
instance from automatic storage into the vector
.
v.push_back(fooBaz);
This doesn't work, because fooBar
and fooBaz
are unrelated types.
Now, prior to c++23 reflection, mimicing what Java does is difficult in C++. You have to do some steps manually.
First, instruct Foo
to understand inheritance when told so manually:
struct empty_t {};
template<class T, class Base=empty_t>
class Foo:Foo<Base> {};
template<>
class Foo<empty_t, empty_t> {
virtual ~Foo() {}
};
struct Bar {};
struct Baz : public Bar {};
auto fooBar = std::make_unique<Foo<Bar>>();
auto fooBaz = std::make_unique<Foo<Baz, Bar>>();
std::vector<std::unique_ptr<Foo<Bar>>> v;
v.push_back(std::move(fooBar));
v.push_back(std::move(fooBaz));
this compiles.
In c++23 compile time reflection should let you auto-detect the base classes of Baz
and have Foo<Baz>
automatically inherit from Foo<Bases>...
if you want.
Now, inheritance is only one kind of way to handle polymorphism in C++, but I think is enough for today.