reign
reign

Reputation: 171

Container of template classes without template parameter

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

Answers (2)

Johan Lundberg
Johan Lundberg

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

skypjack
skypjack

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

Related Questions