3

I have the following simple implementation of a Heterogeneous container:

struct Container {
    struct HolderBase {
    };

    template<typename S>
    struct Holder : HolderBase {
        Holder(S* s) : s_(s) {}
        S* s_;
    };

    template<typename S>
    void push_back(S* s) {
        h_.push_back(new Holder<S>(s));
    }

    vector<HolderBase*> h_;

    template<typename B>
    B* get(int i) {
        //magic here
    }
};

Here's how to use it:

struct ElementBase {};
struct Element : ElementBase {};

int main()
{
    Container container;
    container.push_back(new Element);
    ElementBase* elementBase = container.get<ElementBase>(0);
}

I can add entries of any type to it. But I can't figure out how to implement a function to retrieve elements, as some type, which may be the same as the entry or a base class to it.

What I need seems to be both virtual and template at the same time, which is not possible.

XPlatformer
  • 1,148
  • 8
  • 18
  • 2
    Why "reinvent the wheel" when you can just use [`std::vector`](http://en.cppreference.com/w/cpp/container/vector) with [`std::any`](http://en.cppreference.com/w/cpp/utility/any) (or [Boost any](http://www.boost.org/doc/libs/1_65_1/doc/html/any.html))? Or perhaps do a redesign so you don't need to use heterogeneous containers or type-erasure? – Some programmer dude Dec 12 '17 at 08:47
  • `any` can not do that. An `any_cast` can only cast to the actual type. Not to a base type. – XPlatformer Dec 12 '17 at 12:03

2 Answers2

2

how to implement a function to retrieve elements, as some type, which may be the same as the entry or a base class to it.

To get the same entry, the simplest way preserving your current design is to use RTTI.

firstly, make the type-erasing base polymorphic:

struct HolderBase { virtual ~HolderBase() = default; };

then, you can just dynamic_cast:

template<typename B>
B* get(int i) {
    if( auto holder = dynamic_cast<Holder<B>*>(h_[i]) )
    {
       return holder->s_;
    }
    else
       return nullptr;
}

this will return nullptr whenever the dynamic type of the object pointed to by h_[i] is the wrong type. You may also throw or provide a throwing get<B&> overload as well.

Note that in C++17, we already have std::any (originated from boost.any) basically doing the same, but with a standard interface (to become idiomatic soon) and all details already worked out; so, it's strongly advisable to use it instead of rolling out your own.


The problem of getting an entry as a base is harder though; the simplest solution is to pass the allowed target types to pushback, something like:

template<typename... T,typename S>
void push_back_as(S* s) {
   static_assert( ( std::is_base_of_v<T,S> && ...) );

   h_.push_back(new Holder<S,T...>(s)); // where Holder<S,T0,...> inherits from properly defined Holder<S>,Holder<T0>,...
}

or use some other non-intrusive means for registering the target types (eg. a trait class).

otherwise, I don't think this is generally possible as of now (it will be when we'll have compile-time reflection).

Massimiliano Janes
  • 5,524
  • 1
  • 10
  • 22
2

It doesn't seem possible to have exactly what you want without much pain and inconvenience (for example, registering all classes you want to work with in some kind of central repository).

Here's one way to do almost what you want that can perhaps be useful.

class HolderBase
{
  public:
    virtual ~HolderBase() = default;    
    template <class X> X* get() { return dynamic_cast<X*>(this); }
};

template <class T>
class Holder : public HolderBase, public T
{
  public:
    using T::T;
};

Your container is then just a vector<unique_ptr<HolderBase>> or whatever bunch-of-pointers you fancy.

Test drive:

struct A {
    virtual ~A() = default;
    A(int a) : a(a) {};
    int a;
};

struct B : A {
    B(int a, int b) : A(a), b(b) {};
    int b;
};

struct C : A {
    C(int a, int c) : A(a), c(c) {};
    int c;
};


int main () {
    std::vector<std::unique_ptr<HolderBase>> v;
    v.emplace_back(std::make_unique<Holder<B>>(7,40));
    v.emplace_back(std::make_unique<Holder<C>>(0,42));

    A* a = v[0]->template get<A>();
    B* b = v[0]->template get<B>();
    C* c = v[0]->template get<C>();

    std::cout << a << " " << b << " " << c << "\n";

    a = v[1]->template get<A>();
    b = v[1]->template get<B>();
    c = v[1]->template get<C>();

    std::cout << a << " " << b << " " << c << "\n";
}
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • The main problem with this solution is that the elements can't be constructed, and THEN put into the container. I rewrote the rest of my problem to handle this, and it now work perfectly. Answer accepted. – XPlatformer Dec 15 '17 at 09:26