What is the best way to have an associative array with arbitrary value types for each key in C++?
Currently my plan is to create a "value" class with member variables of the types I will be expecting. For example:
class Value {
int iValue;
Value(int v) { iValue = v; }
std::string sValue;
Value(std::string v) { sValue = v; }
SomeClass *cValue;
Value(SomeClass *v) { cValue = c; }
};
std::map<std::string, Value> table;
A downside with this is you have to know the type when accessing the "Value". i.e.:
table["something"] = Value(5);
SomeClass *s = table["something"].cValue; // broken pointer
Also the more types that are put in Value, the more bloated the array will be.
Any better suggestions?
boost::variant seems exactly what you are looking for.
Your approach was basically into the right direction. You will have to know the type you put into. You can use boost::any
and you will be able to put just about anything into the map, as long as you know what you put into:
std::map<std::string, boost::any> table;
table["hello"] = 10;
std::cout << boost::any_cast<int>(table["hello"]); // outputs 10
Some answers recommended the use of boost::variant
to solve this problem. But it won't let you store arbitrary typed values in the map (like you wanted). You have to know the set of possible types before-hand. Given that, you can do the above more easily:
typedef boost::variant<int, std::string, void*> variant_type;
std::map<std::string, variant_type> table;
table["hello"] = 10;
// outputs 10. we don't have to know the type last assigned to the variant
// but the variant keeps track of it internally.
std::cout << table["hello"];
That works because boost::variant
overloads operator<<
for that purpose. It's important to understand that if you want to save what is currently contained in the variant, you still have to know the type, as with in the boost::any
case:
typedef boost::variant<int, std::string, void*> variant_type;
std::map<std::string, variant_type> table;
table["hello"] = "bar";
std::string value = boost::get<std::string>(table["hello"]);
The order of assignments to a variant is a runtime property of the control flow of your code, but the type used of any variable is determined at compile time. So if you want to get the value out of the variant, you have to know its type. An alternative is to use visitation, as outlined by the variant documentation. It works because the variant stores a code which tells it which type was last assigned to it. Based on that, it decides at runtime which overload of the visitor it uses. boost::variant
is quite big and is not completely standard compliant, while boost::any
is standard compliant but uses dynamic memory even for small types (so it's slower. variant can use the stack for small types). So you have to trade off what you use.
If you actually want to put objects into it which differ only in the way they do something, polymorphism is a better way to go. You can have a base-class which you derive from:
std::map< std::string, boost::shared_ptr<Base> > table;
table["hello"] = boost::shared_ptr<Base>(new Apple(...));
table["hello"]->print();
Which would basically require this class layout:
class Base {
public:
virtual ~Base() { }
// derived classes implement this:
virtual void print() = 0;
};
class Apple : public Base {
public:
virtual void print() {
// print us out.
}
};
The boost::shared_ptr
is a so-called smart pointer. It will delete your objects automatically if you remove them out of your map and nothing else is referencing them them anymore. In theory you could have worked with a plain pointer too, but using a smart pointer will greatly increase safety. Read the shared_ptr manual i linked to.
Subclass Value
with IntValue
, StringValue
, and so on.
Can you use a union with std::map?
Boost::variant provides typeless variables.
Altrnatively you could make all your Value data members private and provide accessors that return an error (or throw) if it isn't set.
A straight-forward optimisation would be to use a union
, since you'll always only have one of the values as key.
A more complete solution would encapsulate some run time type information into a interface. Primarily "Which type is this?" and "How do I compare values for equality?" Then use implementations of that as key.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With