Storing multiple types into class member container

89 Views Asked by At

I was reading this Q/A here and as my question is similar but different I would like to know how to do the following:

Let's say I have a basic non template non inherited class called Storage.

class Storage {};

I would like for this class to have a single container (unordered multimap) is where I'm leaning towards... That will hold a std::string for a name id to a variable type T. The class itself will not be template. However a member function to add in elements would be. A member function to add might look like this:

template<T>
void addElement( const std::string& name, T& t );

This function will then populate the unorderd multimap. However each time this function is called each type could be different. So my map would look something like:

"Hotdogs", 8  // here 8 is int
"Price",  4.85f // here 4.8f is float.

How would I declare such an unorderd multimap using templates, variadic parameters, maybe even tuple, any or variant... without the class itself being a template? I prefer not to use boost or other libraries other than the standard.

I tried something like this:

class Storage {
private:
    template<class T>
    typedef std::unorderd_multimap<std::string, T> DataTypes;

    template<class... T>
    typedef std::unordered_multimap<std::vector<std::string>, std::tuple<T...>> DataTypes;
};

But I can not seem to get the typedefs correct so that I can declare them like this:

{
    DataTypes mDataTypes;
}
2

There are 2 best solutions below

2
On

You tagged C++17, so you could use std::any (or std::variant if the T type can be a limited and know set of types`).

To store the values is simple.

#include <any>
#include <unordered_map>

class Storage
 {
   private:
      using DataTypes = std::unordered_multimap<std::string, std::any>;

      DataTypes mDataTypes;

   public:
      template <typename T>
      void addElement (std::string const & name, T && t)
       { mDataTypes.emplace(name, std::forward<T>(t)); }
 };

int main()
 {
    Storage s;

    s.addElement("Hotdogs", 8);
    s.addElement("Price", 4.85f);

    // but how extract the values ?
 }

But the problem is that now you have a element with "Hotdogs" and "Price" keys in the map, but you have no info about the type of the value.

So you have to save, in some way, a info about the type of th value (transform the value in a std::pair with some id-type and the std::any?) to extract it when you need it.

0
On

I've done something along those lines, the actual solution is very specific to your problem.

That being said, I'm doing this on a vector, but the principle applies to maps, too. If you're not building an API and hence know all classes that will be involved you could use std::variant something along the lines of this:

#include <variant>
#include <vector>
#include <iostream>

struct ex1 {};
struct ex2 {};

using storage_t = std::variant<ex1, ex2>;

struct unspecific_operation {
    void operator()(ex1 arg) { std::cout << "got ex1\n";}
    void operator()(ex2 arg) { std::cout << "got ex2\n";}
};

int main() {
    auto storage = std::vector<storage_t>{};
    storage.push_back(ex1{});
    storage.push_back(ex2{});
    auto op = unspecific_operation{};
    for(const auto& content : storage) {
        std::visit(op, content);
    }
    return 0;
}

which will output:

got ex1
got ex2

If I remember correctly, using std::any will enable RTTI, which can get quite expensive; might be wrong tho.

If you provide more specifics about what you actually want to do with it, I can give you a more specific solution.

for an example with the unordered map:

#include <variant>
#include <unordered_map>
#include <string>
#include <iostream>

struct ex1 {};
struct ex2 {};

using storage_t = std::variant<ex1, ex2>;

struct unspecific_operation {
    void operator()(ex1 arg) { std::cout << "got ex1\n";}
    void operator()(ex2 arg) { std::cout << "got ex2\n";}
};

class Storage {
private:
    using map_t = std::unordered_multimap<std::string, storage_t>;
    map_t data;
public:
    Storage() : data{map_t{}}
    {}

    void addElement(std::string name, storage_t elem) {
        data.insert(std::make_pair(name, elem));
    }

    void doSomething() {
        auto op = unspecific_operation{};
        for(const auto& content : data) {
            std::visit(op, content.second);
        }
    }
};

int main() {
    auto storage = Storage{};
    storage.addElement("elem1", ex1{});
    storage.addElement("elem2", ex2{});
    storage.addElement("elem3", ex1{});
    storage.doSomething();
    return 0;
}