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

Call std::visit with passed lambdas


I have a struct that contains a variant.

I want to write a member function for that struct that should run code depending on which type variant currently holds.

However, I have issues making it compile. I don't want to use more "template shenanigans" like using a separate struct to define operator(T&) since it pollutes the syntax even more. Here is an example:

struct Data {

std::variant<int, double> var;


//Into this function,multiple lambdas should be passed for cases that the user wants to handle
template<typename ... Funcs>
void apply(Funcs&&... funcs) {
    std::visit(std::forward<Funcs>(funcs)...,var);
}
};
int main() {
    Data d;
    d.var = 4;
    //variant holds int and lambda provided that takes int&, execute it:
    d.apply([](int& i){
        std::cout << "I am Int Poggers" << std::endl;
    });
    d.var = 0.0;
    //variant holds double but no lambda passed that takes a double, hence nothing happens:
    d.apply([](int& i){
        std::cout << "I am Int Poggers" << std::endl;
    });
}

and I even don't know what the compiler wants from me: https://godbolt.org/z/oM4584anf


Solution

  • Your problem is that std::visit() needs a "visitor" that must handle every type of the std::variant.

    However, I have issues making it compile. I don't want to use more "template shenanigans" like using a separate struct to define operator(T&) since it pollutes the syntax even more.

    There is nothing complicated.

    You can simply add a trivial struct (with deduction guide) as follows (and as proposed in the cppreference std::visit() page)

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

    Then, given that you want that your std::visit() return void, you can add, in your apply() method, a generic-do-nothing lambda

    template<typename ... Funcs>
    void apply(Funcs&&... funcs) {
        std::visit(overloaded{ // <-- pass through overloaded
                   [](auto const &){}, // <-- and add this generic lambda
                   std::forward<Funcs>(funcs)...},var);
    }
    

    Now the first apply() call

    d.apply([](int& i){
        std::cout << "I am Int Poggers" << std::endl;
    });
    

    should compile calling the supplied lambda because is a better match (given that d contains an int) and the second call compile calling the do-nothing generic lambda, because the generic lambda is a better match for a double.