Fluffy
Fluffy

Reputation: 922

Visit and invoke a variant of std::function

I'm using a std::variant to store different signatures of std::functions. Said functions are stored in a vector, the latter being retrieved from a map.

How do I invoke each function in a vector of std::variant? I feel like I should use std::visit but I can't figure out the correct usage.

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

using var_t = std::variant<std::function<void(void)>, std::function<void(int)>>;
enum class EventEnum { A, B, C };

struct Controller {
    template<EventEnum E>
    void subscribe(var_t fn) {
        auto& callbacksVec = callbacks.at(E);
        callbacksVec.push_back(fn);
    }

    template<EventEnum E>
    void notify() {
        auto& callbacksVec = callbacks.at(E);
        for (auto& func : callbacksVec) {
            // std::visit([](auto& arg){ std::invoke(arg); }, func);
        }
    }

    std::map<EventEnum, std::vector<var_t>> callbacks;
};

int main() {
    auto fn = []() { std::cout << "lambda callback" << std::endl; };
    Controller myController;
    myController.subscribe<EventEnum::A>(fn);
    myController.notify<EventEnum::A>();

    return 0;
}

Upvotes: 5

Views: 3658

Answers (2)

Rakete1111
Rakete1111

Reputation: 48998

std::visit requires that every type inside the variant has a valid "action". If func were to have a std::function<void(int)> instead of std::function<void(void)> as it does right now, then there would be no way act on it. And because this (can) depends on the runtime, std::visit has to check at compile time that every possible alternative of the variant can be used to call your callable.

You can merge lambdas together for example, or have an if constexpr cascade for each type in the variant.

template<typename ...Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<typename ...Ts> overloaded(Ts...) -> overloaded<Ts...>;

std::visit(overloaded{
    [](const std::function<void(void)> &Void) { std::invoke(Void); },
    [](const std::function<void(int)> &Int) { std::invoke(Int, 1); }}, func);

Upvotes: 4

Alexander Stepaniuk
Alexander Stepaniuk

Reputation: 6418

Here is another example, adapted from cppreference page to your situation:

#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
#include <functional>

 using var_t = std::variant<std::function<void(void)>, std::function<void(int)>>;

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main() {
    std::vector<var_t> vec = {
        []() {std::cout << "void" << std::endl;},
        [](int arg) {std::cout << "int " << arg << std::endl;}
    };

    for (auto& v: vec) {
        std::visit(overloaded {
            [](const std::function<void(void)>& fv) { fv(); },
            [](const std::function<void(int)>& fi) { fi(42); }
        }, v);
    }
}

Upvotes: 3

Related Questions