Question

I want to put two (not more) different data types as values into a map as shown in the following example:

typeX A, B, ...;
typeY Z, Y, ...;

void func (typeX) { ... }
void func (typeY) { ... }

std::map <std::string, what_to_put_here??> map;
map["a"] = A;
map["z"] = Z;
...

std::vector<std::string> list;
// This list will be something like "a", "y", ...

for (unsigned int i = 0; i < list.size(); ++i)
    func( map[list[i]] )

Obviously this doesn't work, as the map will only accept one data type of value. However, when looping over list, the call to func() should be unambiguous since the type of map[list[i]] is known.

I want to avoid explicit casting or type checking, i.e. something like this:

if (typeid( map[list[i]] ).name() == "typeX")
    func( map[list[i]] )
else if (typeid( map[list[i]] ).name() == "typeY")
    func( map[list[i]] )

Is this possible? Again, it will be limited to only two different data types.

Was it helpful?

Solution

You want to use boost::variant:

std::map <std::string, boost::variant<typeX, typeY>>

OTHER TIPS

Are typeX and typeY subclasses of a typeBase class ? If so, you could do a std::map<std::string,typeBase*> to store both typeX* and typeY* in the map.

With some metaprogramming you can easily build an heterogenous map which can store any type from a given set of types. Here is an example which does this, without type erasure nor the need to visit the values.

One way to implement a multi-type map is by using the nifty features of std::tuple in C++11, which allows access by a type key. You can wrap this to create access by arbitrary keys. An in-depth explanation of this (and quite an interesting read) is available here:

https://jguegant.github.io/blogs/tech/thread-safe-multi-type-map.html

If you don't want to use Boost, then I think building a class hierarchy as proposed by rom1504 makes sense. I would implement an abstract base class with func() as member function as follows:

class Base {
public:
    virtual void func() = 0;
};

void Base::func() {};

Then, I would turn your typeX and typeY data types into subclasses by deriving from Base as follows:

class typeX : public Base {
public:
    void func() { std::cout << "typeX::func()" << std::endl; };
    int i;  // Example for your original 'typeX' content.
};

class typeY : public Base {
public:
    void func() { std::cout << "typeY::func()" << std::endl; };
    std::string s;  // Example for your original 'typeY' content.
};

Here, the func() implementations would take the content of your corresponding global func() functions. Next, your map needs to store pointers to Base as value:

std::map<std::string, Base*> map;
map["a"] = &A;
map["z"] = &Z;

As a result, you can implement your loop as follows (using a C++11 range-based for loop):

for (auto const &list_item : list)
    map[list_item]->func();

Notes:

  • In case you use dynamically created instances of typeX and typeY, you should prefer to store smart pointers in the map, such as std::unique_ptr or std::shared_ptr, to ease memory management.

  • If you have to stick to your global func() functions, then you can call them from the corresponding member functions. I would just turn the parameter into a pointer or reference to avoid copying objects.

Full code on Ideone

This would probably be extreme overkill, but QT has a variable called QVariant which can be used to map to different types of (QT) variables.

Documentation here: http://qt-project.org/doc/qt-5.0/qtcore/qvariant.html

You need a type erasure.

Type erasure is a pattern that hides the underlying type, such known examples are boost::any, but keep in mind that boost any has a dynamic polymorphic behavior (dynamic dispatch at runtime). boost::variant on the other hand is another example and uses template metaprogramming techniques. see variant vs any

The simplest solution though, could be writing your own class type erasure with an enum for the underlying type.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top