I have the following class using 3 different maps: keys are always strings, while values may be strings, integers or floats.
class MyMaps
{
public:
template<typename T> void addKey(const std::string& key);
void addValue(const std::string& key, const std::string& value);
void addValue(const std::string& key, int value);
void addValue(const std::string& key, float value);
private:
std::map<std::string, std::string> stringFields;
std::map<std::string, int> intFields;
std::map<std::string, float> floatFields;
};
The addValue()
functions simply add a new pair to the related map. What I'm working on is the addKey()
template function:
/** Add only a key, the related value is a default one and is specified by template parameter T. */
template<typename T>
void MyMaps::addKey(const string& key)
{
if (typeid(T) == typeid(string))
{
stringFields.insert(pair<string, string>(key, string()));
}
else if (typeid(T) == typeid(int))
{
intFields.insert(pair<string, int>(key, int()));;
}
else if (typeid(T) == typeid(float))
{
floatFields.insert(pair<string, float>(key, float()));
}
}
Basically, I'm using template
and typeid()
because I don't like this alternative that relies on type-within-function-name:
void MyMaps::addStringKey(const string& key)
{
stringFields.insert(pair<string, string>(key, string()));
}
void MyMaps::addIntKey(const string& key)
{
intFields.insert(pair<string, int>(key, int()));
}
void MyMaps::addFloatKey(const string& key)
{
floatFields.insert(pair<string, float>(key, float()));
}
The first addKey()
version seems working, but I'm wondering if there is a more elegant solution. Maybe I'm missing some Object-Oriented design concept that could be helpful in this case?
Thanks in advance.
The typeid operator allows the type of an object to be determined at run time. The result of typeid is a const type_info& . The value is a reference to a type_info object that represents either the type-id or the type of the expression, depending on which form of typeid is used.
typeid() can be much faster than other type checks if all the stars are aligned, or it can be extremely slow. typeid() requires the compiler to determine if a pointer is null, so if you already know your pointer is non-null, it's more efficient to use typeid() on a reference instead.
The typeid operator returns an lvalue of type const type_info that represents the type of our value. An lvalue has an address that your program can access.
This is a perfect fit for template specialization:
template<>
void MyMaps::addKey<string>(const string& key)
{
stringFields.insert(pair<string, string>(key, string()));
}
template<>
void MyMaps::addKey<int>(const int& key)
{
intFields.insert(pair<string, int>(key, int()));;
}
template<>
void MyMaps::addKey<float>(const float& key)
{
floatFields.insert(pair<string, float>(key, float()));
}
EDIT: For syntax/more info about template specialization read: Template Specialization and Partial Template Specialization
Or better yet, if boost is an option and if the keys are unique for all 3 maps and you have 3 different maps just to be able to store them, then consider using boost::variant
:
typedef boost::variant<string, int, float> ValueType;
class MyMap
{
public:
typedef std::map<std::string, ValueType> MapType;
template<typename T> void addKey(const std::string& key, T &val)
{
ValueType varVal= val;
allFields.insert(MapType::value_type(key, varVal));
}
private:
MapType allFields;
};
Your question inquires 2 things:
The real question, crafting a key value Map or Dictionary, using different types, for the values, in the same collection.
And, a potential solution, applying the "typeid" function.
More reference about "typeid":
http://en.cppreference.com/w/cpp/language/typeid
Object (and Class) Orientation is great, but, sometimes you may want to mix it with other paradigms.
What about "pointers" ?
Pointers allow different types, to be treated as the same simple type.
What about a Key Value Dictionary Collection, that stores a string key, and a pointer value, and the pointer may be integer, string, or object.
Or to be more specific. the "Value" may be a tuple, where the first field (maybe an enumerated type), indicates the real type of the value. And, the second field of the "Value" is a pointer or variant to the real field.
The first suggestion using "Unions" (a.k.a. "variants"), no pointers:
#include <string>
#include <typeinfo>
union ValueUnion
{
int AsInt,
float AsFloat,
std::string& AsStr
};
struct ValueType
{
std::type_info Id,
ValueUnion Value
};
class MyMaps
{
public:
template<typename T> void addKey(const std::string& key);
void addValue(const std::string& key, const std::string& value);
void addValue(const std::string& key, int value);
void addValue(const std::string& key, float value);
private:
std::map<std::string, ValueType> Fields;
};
Or, with pointers:
#include <string>
#include <typeinfo>
struct ValueType
{
std::type_info Id,
void* Value
};
class MyMaps
{
public:
template<typename T> void addKey(const std::string& key);
void addValue(const std::string& key, const std::string& value);
void addValue(const std::string& key, int value);
void addValue(const std::string& key, float value);
private:
std::map<std::string, ValueType> Fields;
};
I have seen this "pattern" several times, I called the "Key-Value-Type" collection.
Note: Not many experience with the STL, are you sure "std::map", is the right collection ?
Just my 2 cents.
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