2

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

max66
  • 65,235
  • 10
  • 71
  • 111
Raildex
  • 3,406
  • 1
  • 18
  • 42
  • 2
    `std::visit` operates with a _singular visitor_, and calls this visitor with _the active member of the supplied variant(s)_, discovering the correct handler by using overload-resolution semantics (which allows conversion to occur). With all that in mind: if you want only a single overload from a lambda to be selected, you will need to write some type that defines `operator()` for the terms you don't want. Also be aware that your `std::visit` call is incorrect since `std::visit` doesn't accept multiple visitor functions (though this is not the cause of your problem) – Human-Compiler Aug 29 '21 at 13:46

1 Answers1

8

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.

max66
  • 65,235
  • 10
  • 71
  • 111