Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ std::map holding ANY type of value

Basically I want MyClass that holds a Hashmap that maps Field name(string) to ANY type of Value.. For this purpose I wrote a separate MyField class that holds the type & value information..

This is what I have so far:

template <typename T> class MyField {     T m_Value;     int m_Size; }   struct MyClass {     std::map<string, MyField> fields;   //ERROR!!! } 

But as you can see, the map declaration fails because I didn't provide the type parameter for MyField...

So I guess It has to be something like

std::map< string, MyField<int> > fields; 

or

std::map< string, MyField<double> > fields; 


But obviously this undermines my whole purpose, because the declared map can only hold MyField of a specific type.. I want a map that can hold ANY type of MyField clas..

Is there any way I can achieve this..?

like image 547
user3794186 Avatar asked Jul 11 '14 16:07

user3794186


People also ask

How does map store values in C++?

Maps are associative containers that store elements in a combination of key values and mapped values that follow a specific order. No two mapped values can have the same key values. In C++, maps store the key values in ascending order by default. A visual representation of a C++ map.

Do maps sort themselves in C++?

No two mapped values can have equal key values. By default, a Map in C++ is sorted in increasing order based on its key.

Can a map have two keys with the same value C++?

You can have a std::map such that multiple keys map to the same value.

Can we search in map using value?

Search by value in a Map in C++Given a set of N pairs as a (key, value) pairs in a map and an integer K, the task is to find all the keys mapped to the give value K. If there is no key value mapped to K then print “-1”. Explanation: The 3 key value that is mapped to value 3 are 1, 2, 10.


2 Answers

This is plain in C++ 17. Use std::map + std::any + std::any_cast:

#include <map> #include <string> #include <any>          int main() {     std::map<std::string, std::any> notebook;      std::string name{ "Pluto" };     int year = 2015;      notebook["PetName"] = name;     notebook["Born"] = year;      std::string name2 = std::any_cast<std::string>(notebook["PetName"]); // = "Pluto"     int year2 = std::any_cast<int>(notebook["Born"]); // = 2015 } 
like image 53
Amit G. Avatar answered Sep 25 '22 01:09

Amit G.


Blindy's answer is very good (+1), but just to complete the answer: there is another way to do it with no library, by using dynamic inheritance:

class MyFieldInterface {     int m_Size; // of course use appropriate access level in the real code...     ~MyFieldInterface() = default; }  template <typename T> class MyField : public MyFieldInterface {     T m_Value;  }   struct MyClass {     std::map<string, MyFieldInterface* > fields;   } 

Pros:

  • it's familiar to any C++ coder
  • it don't force you to use Boost (in some contexts you are not allowed to);

Cons:

  • you have to allocate the objects on the heap/free store and use reference semantic instead of value semantic to manipulate them;
  • public inheritance exposed that way might lead to over-use of dynamic inheritance and a lot of long-term issues related to your types really being too inter-dependent;
  • a vector of pointers is problematic if it have to own the objects, as you have to manage destruction;

So use boost::any or boost::variant as default if you can, and consider this option only otherwise.

To fix that last cons point you could use smart pointers:

struct MyClass {     std::map<string, std::unique_ptr<MyFieldInterface> > fields;  // or shared_ptr<> if you are sharing ownership } 

However there is still a potentially more problematic point:

It forces you to create the objects using new/delete (or make_unique/shared). This mean that the actual objects are created in the free store (the heap) at any location provided by the allocator (mostly the default one). Therefore, going though the list of objects very often is not as fast as it could be because of cache misses.

diagram of vector of polymorphic objects

If you are concerned with performance of looping through this list very often as fast as possible (ignore the following if not), then you'd better use either boost::variant (if you already know all the concrete types you will use) OR use some kind of type-erased polymorphic container.

diagram of polymorphic container

The idea is that the container would manage arrays of objects of the same type, but that still expose the same interface. That interface can be either a concept (using duck-typing techniques) or a dynamic interface (a base class like in my first example). The advantage is that the container will keep same-type objects in separate vectors, so going through them is fast. Only going from one type to another is not.

Here is an example (the images are from there): http://bannalia.blogspot.fr/2014/05/fast-polymorphic-collections.html

However, this technique loose it's interest if you need to keep the order in which the objects are inserted.

In any way, there are several solutions possible, which depends a lot on your needs. If you have not enough experience with your case, I suggest using either the simple solution I first explained in my example or boost::any/variant.


As a complement to this answer, I want to point very good blog articles which summarize all C++ type-erasure techniques you could use, with comments and pros/cons:

  • http://talesofcpp.fusionfenix.com/post-16/episode-nine-erasing-the-concrete
  • http://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/
  • http://akrzemi1.wordpress.com/2013/12/06/type-erasure-part-ii/
  • http://akrzemi1.wordpress.com/2013/12/11/type-erasure-part-iii/
  • http://akrzemi1.wordpress.com/2014/01/13/type-erasure-part-iv/
like image 44
Klaim Avatar answered Sep 25 '22 01:09

Klaim