Heterogeneous Containers in C++

Heterogeneous containers in C++

Well generally C++ Containers are designed to hold objects of a single type using templates. If you want different types that are all derived from one type you can store a container of pointers (I guess you could also have a container of void* to anything...) e.g. std::vector<MyBaseType*>.

If you want completely unrelated types, you can store objects that can safely reference those other types, such as boost::any.

http://www.boost.org/doc/libs/1_47_0/doc/html/any.html

Some examples off the boost site:

#include <list>
#include <boost/any.hpp>

using boost::any_cast;
typedef std::list<boost::any> many;

void append_int(many & values, int value)
{
boost::any to_append = value;
values.push_back(to_append);
}

void append_string(many & values, const std::string & value)
{
values.push_back(value);
}

bool is_int(const boost::any & operand)
{
return operand.type() == typeid(int);
}
bool is_char_ptr(const boost::any & operand)
{
try
{
any_cast<const char *>(operand);
return true;
}
catch(const boost::bad_any_cast &)
{
return false;
}
}

boost::variant is similar, but you specify all the allowed types, rather than allowing any type in your container.

http://www.boost.org/doc/libs/1_47_0/doc/html/variant.html

std::vector< boost::variant<unsigned, std::string> > vec;
vec.push_back( 44);
vec.push_back( "str" );
vec.push_back( SomthingElse(55, 65) ); //not allowed

c++ heterogeneous container, get entry as type

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";
}

std::variant vs pointer to base class for heterogeneous containers in C++

std::variant<A,B,C> holds one of a closed set of types. You can check whether it holds a given type with std::holds_alternative, or use std::visit to pass a visitor object with an overloaded operator(). There is likely no dynamic memory allocation, however, it is hard to extend: the class with the std::variant and any visitor classes will need to know the list of possible types.

On the other hand, BaseClass* holds an unbounded set of derived class types. You ought to be holding std::unique_ptr<BaseClass> or std::shared_ptr<BaseClass> to avoid the potential for memory leaks. To determine whether an instance of a specific type is stored, you must use dynamic_cast or a virtual function. This option requires dynamic memory allocation, but if all processing is via virtual functions, then the code that holds the container does not need to know the full list of types that could be stored.

Printing from a custom heterogeneous container in C++

As you store element by type, you loose insertion order, so you have to change

template<class T>
static std::unordered_map<const heterogeneous_container*, std::vector<T>> items;

by

template <class T>
static std::unordered_map<const heterogeneous_container*,
std::vector<std::pair<std::size_t, T>>> items;

Add a visit_ordered:

template <typename T, class V>
void visit_with_index_impl_help(V& visitor)
{
for (auto&& p : items<T>[this])
{
visitor(p.first, p.second);
}
}
template <class V, typename... Ts>
void visit_ordered_impl(V& visitor, type_list<Ts...>)
{
std::vector<std::pair<std::size_t, std::function<void()>> funcs;
const auto inserter = [&](std::size_t i, auto&& elem) {
funcs.emplace_back(i, visitor(elem));
};

(..., visit_with_index_impl_help<Ts>(inserter));
const auto less_by_first = [](const auto& lhs, const auto& rhs){ return lhs.first < rhs.first; };
std::sort(funcs.begin(), funcs.end(), less_by_first);
for (auto&& func : funcs) {
func();
}
}

template <class V>
void visit_ordered(V& visitor)
{
visit_ordered_impl(visitor, typename std::decay_t<V>::types{})
}

C++ How to create a heterogeneous container

The boost library has probably what you're looking for (boost::any). You can roll your own using a wrapped pointer approach if you cannot use boost...

C++ container for heterogeneous POD types

With C++17 you can use std::any with any container. With older C++ versions you can use boost::any.

#include <iostream>
#include <vector>
#include <any>

struct A
{
int a;
explicit operator int() const { return a; }
};

struct B
{
double b;
explicit operator double() const { return b; }
};

int main()
{
A a{ 5 };
B b{ 6.};

std::vector<std::any> v;
v.push_back(3 );
v.push_back(4.);
v.push_back(a );
v.push_back(b );

for (auto const e : v)
{
if (e.type() == typeid(double))
std::cout << std::any_cast<double>(e) << std::endl;

if (e.type() == typeid(B))
std::cout << (double)std::any_cast<B>(e) << std::endl;
}
}


Related Topics



Leave a reply



Submit