3

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?

reign
  • 71
  • 6
  • "my current implementation makes it important that such a [heterogeneous] list exists". Oh. That's an anti-pattern. You need to ditch that design. – Cheers and hth. - Alf Jul 05 '16 at 22:10
  • No you can't. `base` is a *completely different type* to `base`. And you can't store a derived class object in a base class variable anyway. – user253751 Jul 05 '16 at 23:11

2 Answers2

3

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.

Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
3

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.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • This is acceptable as long as there's only one relevant operation per class, such as `process event`. – Johan Lundberg Jul 05 '16 at 22:51
  • @JohanLundberg OP wants to process `base` for each `T`, nothing more. As he mentioned in a comment to your answer, he *wants* to use `val`. This way he can use it. Double dispatching fits well in this case. What's exactly the problem? – skypjack Jul 05 '16 at 23:01
  • @skypjack, The 'problem' if you would call it that is the burden when you would like to do other things in addition to cout for each type. Your answer is fine. – Johan Lundberg Jul 05 '16 at 23:09