Хранение нескольких типов в контейнере члена класса
Я читал это 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;
}