My solution for multiple visitation. Thanks to Jarod42 for showing me the way with single variant visitation.
Live Demo
The main problem is to generate the cross-product of all possible calls to an overload set.
This answer does not address the problem of a generic conversion of return types, I just did an ad-hoc specialization of std::common_type
(I think this is enougth to suit 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 (std::index_sequence
anyone?)
#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";
}