Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic container for multiple data types in C++

Using C++, I'm trying to create a generic container class to handle multiple data types. It's a common problem with a variety of solutions, but I've found nothing as... intuitive as I've grown accustomed to in languages like Python or even VB/VBA...

So here's my scenario:

I've built a DataContainer class based on boost::any which I use to store multiple data types of multiple elements. I use a map declared as:

std::map<std::string, DataContainer* (or DataContainerBase*)>

where DataContainer is a class that encapsulates an object of the type:

std::list<boost::any>

along with convenience functions for managing / accessing the list.

However, in the end, I'm still forced to do type conversions outside the data container.

For example, if I were to store a list of int values in the map, accessing them would require:

int value = boost::any_cast<int>(map["myValue"]->get());

I'd rather the boost code be contained entirely within the data container structure, so I would only need type:

int value = map["myValue"]->get();

or, worst-case:

int value = map["myValue"]->get<int>();

Of course, I could enumerate my data types and do something like:

int value = map["myValue"]->get( TYPE_INT );

or write type-specific get() functions:

getInt(), getString(), getBool() ... 

The problem with the last two options is that they are somewhat inflexible, requiring me to declare explicitly each type I wish to store in the container. The any_cast solution (which I have implemented and works) I suppose is fine, it's just... inelegant? I dunno. It seems I shouldn't need to employ the internal mechanics externally as well.

As I see it, passing the value without declaring the value type in the call to the DataContainer member function would require a void* solution (which is undesirable for obvious reasons), and using a "get()" call would require (so far as I can tell) a "virtual template" member function defined at the base class level, which, of course, isn't allowed.

As it is, I have a workable solution, and really, my use in this case is limited enough in scope that most any solutions will work well. But I am wondering if perhaps there's a more flexible way to manage a generic, multi-type data container than this.

like image 873
Joel Graff Avatar asked Dec 20 '11 00:12

Joel Graff


1 Answers

If you want to introduce some sugar for this:

int value = boost::any_cast<int>(map["myValue"]->get());

then you might want to make the get() function to return a proxy object, defined +- like this:

struct Proxy {
    boost::any& value;
    Proxy(boost::any& value) : value(value) {}

    template<typename T>
    operator T() {
        return boost::any_cast<T>(value);
    }
};

Then this syntax would work:

int value = map["myValue"]->get();
// returns a proxy which gets converted by any_cast<int>

However I recommend to keep things explicit and just use that syntax:

int value = map["myValue"]->get<int>();

Here get doesn't return a proxy object with a template method, but is a template method itself (but does the same as the template conversion operator shown above).

like image 135
Kos Avatar answered Sep 22 '22 20:09

Kos