6

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.

5
  • How will you know what types each of the values should have? Are you going to remember that vote is always an unsigned int, or are you going to try to parse it and notice that it's entirely numeric and there's not decimal point? Commented Nov 21, 2017 at 3:52
  • There's a better way to do what you're trying, but it's not clear what you're really trying to achieve. Compile time happens before runtime. If a runtime string determines the type you're getting, the compiler isn't going to know what type it is. It won't be able to generate the proper code. Commented Nov 21, 2017 at 4:03
  • I think you need to revisit how you're organizing your data. Mixing like this caused me endless headaches. Plus I was doing it wrong :( Commented Nov 21, 2017 at 4:35
  • The different data types can all be decided at compile time. Name will always be a string, vote will always be an int, etc Commented Nov 21, 2017 at 11:48
  • Thank you all, this is very helpful Commented Nov 21, 2017 at 15:21

4 Answers 4

6

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.

Sign up to request clarification or add additional context in comments.

Comments

4

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

Comments

1

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.

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.