Domanda

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.

È stato utile?

Soluzione

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;                              
};

Altri suggerimenti

Your question inquires 2 things:

  1. The real question, crafting a key value Map or Dictionary, using different types, for the values, in the same collection.

  2. 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.

Might not be what you want because it's a different approach but you could use a map of variants. you can define a boost::variant to hold only string,int or float.

eladidan beat me to it and I don't know how to delete answers.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top