Reputation: 171
I'm wondering if you can have a container with objects with varying template parameters.
I'm trying to achieve something like this:
#include <iostream>
#include <list>
template <class T>
class base
{
public:
T val;
base(T newVal): val(newVal) {};
};
class derived : public base<int>
{
public:
derived(int newVal): base(newVal) {};
};
int main ( void )
{
std::list < base<?> > base_collection;
return 0;
}
I want my current project to be as flexible and dynamic as possible, with little extra coding when a new derived class is necessary, and my current implementation makes it important that such a list exists.
Is there a commonly used, benefiting and clean way of achieving exactly this?
Upvotes: 3
Views: 2261
Reputation: 27028
It's not completely clear why you need to do this, or what operations you intend to perform on the elements of the list (btw, consider using std vector instead). I suggest you make a common non-templated base class that base inherits from:
struct mainbase {
virtual ~mainbase() = default;
};
template <class T>
class base : public mainbase
{
public:
T val;
base(T newVal): val(newVal) {};
};
class derived : public base<int>
{
public:
derived(int newVal): base(newVal) {};
};
int main ( void )
{
std::list < std::unique_ptr<mainbase>> > base_collection;
return 0;
}
After all, if you're going to put them all in a vector, you most likely require a common set of operations which you can perform with those objects. Put those in mainbase
.
As @BenjaminLindley points out, you can't have polymorphism by-value. That's why you would use a pointer (such as unique_ptr): std::unique_ptr<mainbase>
.
With C++17 there's a proposal (on track) for std::any, which could be used instead, but you would still have to perform a specific cast to get the content with the correct type.
Upvotes: 3
Reputation: 50550
A possible implementation would be using the double dispatching:
#include <iostream>
#include <list>
struct visitor;
struct dispatchable {
virtual void accept(visitor &v) = 0;
};
template <class>
struct base;
struct visitor {
template<typename T>
void visit(base<T> &);
};
template <class T>
struct base: dispatchable {
T val;
base(T newVal): val(newVal) {};
void accept(visitor &v) override { v.visit(*this); }
};
struct derivedInt : base<int> {
derivedInt(int newVal): base(newVal) {};
};
struct derivedDouble : base<double> {
derivedDouble(double newVal): base(newVal) {};
};
template<>
void visitor::visit(base<int> &) {
std::cout << "int" << std::endl;
}
template<>
void visitor::visit(base<double> &) {
std::cout << "double" << std::endl;
}
int main ( void ) {
visitor v{};
std::list <dispatchable*> coll;
coll.push_back(new derivedInt{42});
coll.push_back(new derivedDouble{.42});
for(auto d: coll) d->accept(v);
}
This way, you have only to define the specialized function that deals with the new base<T>
type you want to introduce.
As an example, if you want to use base<char>
, you have to define:
template<>
void visitor::visit(base<char> &) {
std::cout << "char" << std::endl;
}
Note that I supposed you want to treat each specialization of base<T>
in a different way. Otherwise, it's enough to define the generic member function visitor::visit
and drop the specializations.
Side note: do not use naked pointers.
This is an example. In production code, I'd use smart pointers instead.
Upvotes: 3