Container of template classes without template parameter


Keywords:c++ 


Question: 

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?


2 Answers: 

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.



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.