The way you'd do this in C++ would be to only use the template type in the input and output interfaces of your class.
struct base_pointer_container {
std::vector<Base*> data;
void append(Base* t) {
data.push_back(t);
}
T* operator[](std::size_t n) const {
Assert( n < data.size() );
return data[n];
}
};
template<typename T>
struct pointer_container:private base_pointer_container {
void append(Base* t) {
static_assert( std::is_base_of<Base,T>::value, "derivation failure" );
return base_pointer_container::append(t);
}
T* operator[](std::size_t n) const {
return static_cast<T*>(base_pointer_container::operator[](n));
}
};
This is somewhat analogous to what Java does under the hood, if I understand their Generics properly: the Derived
version is merely a thin wrapper around the Base
version that does type casting on input/output operations.
C++ template
s are much more than this. The entire class is written anew for each new set of type arguments, and the implementations can, in theory, be completely different. There is no relationship between a std::vector<int>
and a std::vector<long>
-- they are unrelated class
es -- except that they can both be pattern-matched as std::vector
s, and they share many properties.
This level of power is only rarely needed. But for an example of how it can be extremely powerful, most standard libraries use it to create type-erasure objects in std::function
. std::function
can take any language element for which operator()
is defined and compatible with its signature, regardless of the run-time layout or design of the type, and erase (hide) the fact that its type is distinct.
So a function pointer, a lambda, a functor written by some programmer in tibet -- despite having no run time layout compatibilities, a std::function< bool() >
can store all of them.
It (typically) does this by creating a custom holder object for each type T
that has its own copy, move, construct, destruct and call code. Each of those is custom-compiled for the type in question. It then stores a copy of the object within itself, and exposes a virtual
interface to each of these operations. std::function
then holds a pointer to the parent interface of that custom object holder, and then exposes its "value-like" interface to the end user (you).
This mixture of pImpl
and template
duck type code generation and template
constructors is known as type-erasure in C++, and it allows C++ to not have the common "object" root (with the entailed restrictions on run time object layout) that Java has without much in the way of sacrifice.