Search code examples
c++c++17stdmapvariantstd-variant

Iterate through a map of std::variant


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à?


Solution

  • 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.