My solution for multiple visits. Thanks to Jarod42 for showing me the way with a visit to one option.
Live demo
The main problem is to generate a cross product of all the possible calls to the overload set. This answer does not address the issue of general type conversion of returned data, I just made a special specialization std::common_type (I think it is necessary to meet my needs, but feel free to contribute!).
See compile-time tests at the end to understand each template meta function.
Feel free to suggest simplifications (is anyone std::index_sequence ?)
#include <variant> #include <iostream> #include <type_traits> // ========= Library code ========= // // --- Operations on types --- // template<class... Ts> struct Types; // used to "box" types together // Lisp-like terminology template<class Head, class Tail> struct Cons_types; template<class Head, class... Ts> struct Cons_types<Head,Types<Ts...>> { using type = Types<Head,Ts...>; }; template<class... _Types> struct Cat_types; template<class _Types, class... Other_types> struct Cat_types<_Types,Other_types...> { using type = typename Cat_types<_Types, typename Cat_types<Other_types...>::type>::type; }; template<class... T0s, class... T1s> struct Cat_types< Types<T0s...> , Types<T1s...> > { using type = Types< T0s..., T1s... >; }; template<class... T0s> struct Cat_types< Types<T0s...> > { using type = Types< T0s... >; }; template<class Head, class Types_of_types> struct Cons_each_types; template<class Head, class... Ts> struct Cons_each_types<Head,Types<Ts...>> { using type = Types< typename Cons_types<Head,Ts>::type... >; }; template<class Head> struct Cons_each_types<Head,Types<>> { using type = Types< Types<Head> >; }; template<class _Types> struct Cross_product; template<class... Ts, class... Other_types> struct Cross_product< Types< Types<Ts...>, Other_types... > > { using type = typename Cat_types< typename Cons_each_types<Ts,typename Cross_product<Types<Other_types...>>::type>::type...>::type; }; template<> struct Cross_product<Types<>> { using type = Types<>; }; // --- Operations on return types --- // template<class Func, class _Types> struct Common_return_type; template<class Func, class... Args0, class... Other_types> struct Common_return_type<Func, Types< Types<Args0...>, Other_types... >> { using type = std::common_type_t< std::result_of_t<Func(Args0...)>, // C++14, to be replaced by std::invoke_result_t in C++17 typename Common_return_type<Func,Types<Other_types...>>::type >; }; template<class Func, class... Args0> struct Common_return_type<Func, Types< Types<Args0...> >> { using type = std::result_of_t<Func(Args0...)>; }; // --- Operations on variants --- // template<class... Vars> struct Vars_to_types; template<class... Ts, class... Vars> struct Vars_to_types<std::variant<Ts...>,Vars...> { using type = typename Cons_types< Types<Ts...> , typename Vars_to_types<Vars...>::type >::type; }; template<> struct Vars_to_types<> { using type = Types<>; }; template<class Func, class... Vars> // requires Func is callable // requires Args are std::variants struct Common_return_type_of_variant_args { using Variant_args_types = typename Vars_to_types<Vars...>::type; using All_args_possibilities = typename Cross_product<Variant_args_types>::type; using type = typename Common_return_type<Func,All_args_possibilities>::type; }; template <typename Func, class... Args> // requires Args are std::variants decltype(auto) visit_ext(Func&& f, Args... args) { using Res_type = typename Common_return_type_of_variant_args<Func,Args...>::type; return std::visit( [&](auto&&... e) -> Res_type { return f(std::forward<decltype(e)>(e)...); }, std::forward<Args>(args)...); } // ========= Application code ========= // struct A { int i; }; struct B { int j; }; // This part is not generic but is enough namespace std { template<> struct common_type<A,B> { using type = std::variant<A,B>; }; template<> struct common_type<B,A> { using type = std::variant<A,B>; }; template<> struct common_type<A,std::variant<A,B>> { using type = std::variant<A,B>; }; template<> struct common_type<std::variant<A,B>,A> { using type = std::variant<A,B>; }; template<> struct common_type<B,std::variant<A,B>> { using type = std::variant<A,B>; }; template<> struct common_type<std::variant<A,B>,B> { using type = std::variant<A,B>; }; } struct Functor { auto operator()(A a0,A a1) -> A { return {a0.i+2*a1.i}; } auto operator()(A a0,B b1) -> A { return {3*a0.i+4*b1.j}; } auto operator()(B b0,A a1) -> B { return {5*b0.j+6*a1.i}; } auto operator()(B b0,B b1) -> B { return {7*b0.j+8*b1.j}; } }; // ========= Tests and final visit call ========= // int main() { std::variant<A,B> var0; std::variant<A,B> var1; using Variant_args_types = typename Vars_to_types<decltype(var0),decltype(var1)>::type; static_assert( std::is_same_v< Types< Types<A,B>, Types<A,B> >, Variant_args_types > ); using Cons_A_Nothing = typename Cons_each_types<A, Types<> >::type; static_assert( std::is_same_v< Types< Types<A> >, Cons_A_Nothing > ); using Cons_A_AB = typename Cons_each_types<A, Types<Types<A>,Types<B>> >::type; using Cons_B_AB = typename Cons_each_types<B, Types<Types<A>,Types<B>> >::type; static_assert( std::is_same_v< Types< Types<A,A>, Types<A,B> >, Cons_A_AB > ); using Cat_types_A = typename Cat_types<Cons_A_Nothing>::type; static_assert( std::is_same_v< Types< Types<A> >, Cat_types_A > ); using Cat_types_AA_AB_BA_BB = typename Cat_types<Cons_A_AB,Cons_B_AB>::type; static_assert( std::is_same_v< Types< Types<A,A>, Types<A,B>, Types<B,A>, Types<B,B> >, Cat_types_AA_AB_BA_BB > ); using Depth_x1_1_cross_product = typename Cross_product<Types<Types<A>>>::type; static_assert( std::is_same_v< Types< Types<A> >, Depth_x1_1_cross_product > ); using Depth_x2_1_1_cross_product = typename Cross_product<Types<Types<A>,Types<B>>>::type; static_assert( std::is_same_v< Types< Types<A,B> >, Depth_x2_1_1_cross_product > ); using All_args_possibilities = typename Cross_product<Variant_args_types>::type; static_assert( std::is_same_v< Types< Types<A,A>, Types<A,B>, Types<B,A>, Types<B,B> >, All_args_possibilities > ); using Functor_AorB_AorB_common_return_type = typename Common_return_type<Functor,All_args_possibilities>::type; static_assert( std::is_same_v< std::variant<A,B>, Functor_AorB_AorB_common_return_type > ); using Functor_varAB_varAB_common_return_type = typename Common_return_type_of_variant_args<Functor,decltype(var0),decltype(var1)>::type; static_assert( std::is_same_v< std::variant<A,B>, Functor_varAB_varAB_common_return_type > ); var0 = A{42}; var1 = A{43}; auto res0 = visit_ext(Functor(), var0,var1); std::cout << "res0 = " << std::get<A>(res0).i << "\n"; var0 = A{42}; var1 = B{43}; auto res1 = visit_ext(Functor(), var0,var1); std::cout << "res1 = " << std::get<A>(res1).i << "\n"; var0 = B{42}; var1 = A{43}; auto res2 = visit_ext(Functor(), var0,var1); std::cout << "res2 = " << std::get<B>(res2).j << "\n"; var0 = B{42}; var1 = B{43}; auto res3 = visit_ext(Functor(), var0,var1); std::cout << "res3 = " << std::get<B>(res3).j << "\n"; }