std::map with different data types for values

自闭症网瘾萝莉.ら 提交于 2020-12-31 09:29:44

问题


I have been searching the forums and google and having a hard time understanding how I can do what I want.

My example is based of typical dataset you see for an election. I want to split a delimited string and create a map to access later The string looks like this: "name=candidate1;vote=1000;percent=10.5"

I am able to create my map of strings as follows

    while (getline(oss, key, '=') && getline(oss, value))
    {

      mCanData.insert(std::pair<std::string, std::string>(key, value));

    }

What I would like to do, and I do not know if this is possible, is to insert the values in the map with different datatypes(i.e.key = "name" value ="candidate1", key = "vote" value =1000, key="percent" value=10.5). The map I want to create will set a private class variable that can be accessed later through a getter by other classes. I am not able to use the boost library so please do not suggest that.

Any help would be great as I am lost right now. If there is a better way to go about this I would like to know that as well.


回答1:


In a C++ std::map, all the values have the same type.

Generally, when you have structured data like that, you want to define a structured type:

class Vote {
  public:
    std::string name;
    int         vote;
    double      percent;
};



回答2:


If you really want to put not structured data in your map, in C++17 you can use std::variant to do that and thus visit it to get back your data.
It follows a minimal, working example:

#include <variant>
#include <string>
#include <map>
#include <iostream>

int main() {
    std::map<std::string, std::variant<std::string, int, double>> mm;
    mm["name"] = "candidate1";
    mm["vote"] = 1000;
    mm["percent"] = 10.5;

    auto visitor = [](auto data){ std::cout << data << std::endl; };
    std::visit(visitor, mm["name"]);
    std::visit(visitor, mm["vote"]);
    std::visit(visitor, mm["percent"]);
}

See it up and running on wandbox.
If it works for you mostly depends on the fact that you can use or not C++17. You didn't specify it, so it's hard to say.


That being said, structured data (as suggested by @rici) looks like a far better solution to the problem.
However we cannot say neither what's the real problem nor how you designed the rest of the code, so it's worth it mentioning also std::variant probably.




回答3:


As mentioned by Rici above the correct approach should be to have a user defined type such as a class or struct and use it as key. In your case you can even just use set instead of map. Do remember to provide your comparator function or a < operator for your user defined type. So your design will look something like below.

struct Vote {
    std::string name;
    int         vote;
    double      percent;
};

struct myComparator
{
    bool operator() (const Vote& obj1, const Vote& obj2)
    {
      // Write your comparison logic which will be used by your container while inserting the data
    }
};

int main()
{
    std::set<Vote, myComparator> mySet;
    // Do your insertion and other operations.
    return 0;
}



回答4:


Try this with no C++ version dependency:

#include <iostream>
#include <map>
#include <cstdlib>


class Vote {
public:
    enum DATA_TYPE
    {
        STRING,
        INT,
        DOUBLE
    };
    Vote(int value)
        : m_type(DATA_TYPE::INT), m_ptr(new int(value)){}
    Vote(std::string& value)
        : m_type(DATA_TYPE::STRING), m_ptr(new std::string(value)){}
    Vote(double value)
        : m_type(DATA_TYPE::DOUBLE), m_ptr(new double(value)){}
    int toInt() {
        switch (m_type) {
        case DATA_TYPE::INT:
            return *(static_cast<int*>(m_ptr));
            break;
        case DATA_TYPE::DOUBLE:
            return (int)*(static_cast<double*>(m_ptr));
            break;
        case DATA_TYPE::STRING:
            return string2int(*(static_cast<std::string*>(m_ptr)));
        }
    }
    double toDouble() {
        switch (m_type) {
        case DATA_TYPE::INT:
            return (double)*(static_cast<int*>(m_ptr));
            break;
        case DATA_TYPE::DOUBLE:
            return *(static_cast<double*>(m_ptr));
            break;
        case DATA_TYPE::STRING:
            return string2double(*(static_cast<std::string*>(m_ptr)));
        }
    }
    std::string toString() {
        switch (m_type) {
        case DATA_TYPE::INT:
            return int2string(*(static_cast<int*>(m_ptr)));
            break;
        case DATA_TYPE::DOUBLE:
            return double2string(*(static_cast<double*>(m_ptr)));
            break;
        case DATA_TYPE::STRING:
            return *(static_cast<std::string*>(m_ptr));
        }
    }
    int string2int(std::string str)
    {
        return atoi(str.c_str());
    }
    double string2double(std::string str)
    {
        return atof(str.c_str());
    }
    std::string int2string(int value)
    {
        char buffer[10];
        itoa(value, buffer, 10);
        return std::string(buffer);
    }
    std::string double2string(double value)
    {
        char buffer[24];
        sprintf(buffer,"%f", value);
        return std::string(buffer);
    }
    DATA_TYPE type()
    {
        return m_type;
    }
private:
    std::string m_strValue;
    int m_intValue;
    double m_douValue;
    DATA_TYPE m_type;
    void *m_ptr;
};

int main()
{

    std::map<std::string, Vote> map;
    map.insert(std::pair<std::string, Vote>("Data1", 2));
    map.insert(std::pair<std::string, Vote>("Data2", 2.2));
    std::string Data3Value("1.31232");
    map.insert(std::pair<std::string, Vote>("Data3", Data3Value));

    for (std::map<std::string, Vote>::iterator it_map = map.begin();
         it_map != map.end();
         it_map ++) {
        Vote::DATA_TYPE type = (*it_map).second.type();
        switch (type) {
        case Vote::DATA_TYPE::INT:
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toInt() << std::endl;
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toDouble() << std::endl;
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toString() << std::endl;
            break;
        case Vote::DATA_TYPE::DOUBLE:
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toInt() << std::endl;
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toDouble() << std::endl;
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toString() << std::endl;
            break;
        case Vote::DATA_TYPE::STRING:
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toInt() << std::endl;
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toDouble() << std::endl;
            std::cout << "Key: " << (*it_map).first << ", value: " << (*it_map).second.toString() << std::endl;
            break;
        }

    }
    return 0;
}

You can provide 3 type constructor and then you can accept 3 types to construct the objects, and then provide a void pointer, point to the data we "new" when constructing the object. When we call toInt(), toDouble(), toString() functions and use static_cast to cast pointer to the data type we want.



来源:https://stackoverflow.com/questions/47404870/stdmap-with-different-data-types-for-values

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!