I'm experimenting with C++17's std::variant to store data of multiple types in a map. The use-case here is to have a map of controllers of generic types (but bound by std::variant
) that I can iterate through and call methods of.
In below example,
#include <iostream>
#include <map>
#include <variant>
class ControlA {
public:
void specificToA() { std::cout << "A" << std::endl; }
};
class ControlB {
public:
void specificToB() { std::cout << "B" << std::endl; }
};
template<typename T>
class ControlItem{
T* control;
public:
ControlItem() = default;
~ControlItem() = default;
void doStuff() {
if constexpr (std::is_same_v<T, ControlA>) {
control->specificToA();
}
if constexpr (std::is_same_v<T, ControlB>) {
control->specificToB();
}
}
};
class MyClass {
public:
void cycleThroughMap();
std::map<std::string, std::variant<ControlItem<ControlA>, ControlItem<ControlB>>> controlMap;
};
The heuristic method for this would be to get the mapped value of each declared type like:
void MyClass::cycleThroughMap() {
for (auto controlItem : controlMap) {
if (auto control = std::get_if<ControlItem<ControlA>>(&controlItem.second)) {
control->doStuff();
} else if (auto control = std::get_if<ControlItem<ControlB>>(&controlItem.second)) {
control->doStuff();
} else
std::cout << "Unknown type!" << std::endl;
}
}
This works but feels like it's not meant to exist.
Can std::variant
be used for this? Is it a bad idea from the start, should I use inheritance and voilà?
Can
std::variant
be used for this?
Yes. Your code is primed for using a variant effectively. The variant holds types with the same implicit interface. It's a perfect opportunity to use std::visit
with a generic lambda.
void MyClass::cycleThroughMap() {
for (auto& [ key, control ] : controlMap) {
std::visit([](auto& c) {
c.doStuff();
}, control);
}
}
I also took the liberty of replacing the pair access with a structured binding. For some added simplicity.