Хранение нескольких типов в контейнере члена класса

Я читал это Q/A здесь, и так как мой вопрос похож, но отличается, я хотел бы знать, как сделать следующее:

Допустим, у меня есть базовый не наследуемый не шаблонный класс Storage,

class Storage {};

Я хотел бы, чтобы в этом классе был один контейнер (неупорядоченная мультикарта), куда я склоняюсь... Это будет содержать std::string для имени идентификатора переменной типа T. Сам класс не будет шаблоном. Однако функция-член для добавления в элементы будет. Добавляемая функция-член может выглядеть так:

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

Эта функция затем заполнит неупорядоченную мультикарту. Однако каждый раз, когда эта функция вызывается, каждый тип может отличаться. Так что моя карта будет выглядеть примерно так:

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

Как бы я объявил такую ​​неупорядоченную мультикарту, используя шаблоны, переменные параметры, может быть, даже кортеж, любой или вариант... без того, чтобы сам класс был шаблоном? Я предпочитаю не использовать boost или другие библиотеки, кроме стандартных.

Я попробовал что-то вроде этого:

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

Но я не могу получить правильные определения типов, чтобы я мог объявить их так:

{
    DataTypes mDataTypes;
}

2 ответа

Вы пометили C++17, чтобы вы могли использовать std::any (или же std::variant если T Тип может быть ограниченным и знать множество типов`).

Хранить значения просто.

#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 ?
 }

Но проблема в том, что теперь у вас есть элемент с ключами "Hotdogs" и "Price" на карте, но у вас нет информации о типе значения.

Таким образом, вы должны каким-то образом сохранить информацию о типе значения th (преобразовать значение в std::pair с некоторым идентификатором типа и std::any?) чтобы извлечь его, когда вам это нужно.

Я сделал что-то в этом роде, фактическое решение очень специфично для вашей проблемы.

При этом я делаю это на векторе, но принцип применим и к картам. Если вы не создаете API и, следовательно, знаете все участвующие классы, вы можете использовать std::variant что-то вроде этого:

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

который выведет:

got ex1
got ex2

Если я правильно помню, используя std::any включит RTTI, который может стать довольно дорогим; может быть не так, хотя.

Если вы предоставите более подробную информацию о том, что вы на самом деле хотите с этим сделать, я могу дать вам более конкретное решение.

для примера с неупорядоченной картой:

#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;
}
Другие вопросы по тегам