简体   繁体   中英

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

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.

Another way to structure the code - removes the need for get_if. Comments inline:

#include <map>
#include <variant>
#include <iostream>

class ControlA {
public:
    void specificToA() { std::cout << "A" << std::endl; }
};

// consistent free-function interface for each operation type allows ADL lookup
void adlDoStuff(ControlA& c)
{
    // but with different implementation details
    c.specificToA();
}

class ControlB {
public:
    void specificToB() { std::cout << "B" << std::endl; }
};

// consistent free-function interface for each operation type allows ADL lookup
void adlDoStuff(ControlB& c)
{
    // but with different implementation details
    c.specificToB();
}

template<typename T>
class ControlItem{
    T* control;

public:
    ControlItem() = default;
    ~ControlItem() = default;

    void doStuff() {
        // invoke the adl-friendly free functions.
        adlDoStuff(*control);
    }
};

class MyClass {
public:
    void cycleThroughMap();
    std::map<std::string, std::variant<ControlItem<ControlA>, ControlItem<ControlB>>> controlMap;
};

void MyClass::cycleThroughMap() {
    // use std::visit. Every type of control will have the .doStuff interface
    for (auto&& elem : controlMap) {
        std::visit([](auto&& control)
        {
            control.doStuff();
        }, elem.second);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM