Moje rozwiązanie dla wielu odwiedzin. Dzięki Jarod42 za pokazanie mi drogi z pojedynczą wizytówką wariantu.
Live Demo
Głównym problemem jest to, aby wygenerować przekrój iloczyn wszystkich możliwych połączeń na zestawie przeciążenia. Ta odpowiedź nie rozwiązuje problemu ogólnej konwersji typów zwrotów. Właśnie wykonałem specjalizację ad-hoc: std::common_type
(uważam, że jest to wystarczające, aby spełnić moje potrzeby, ale nie wahaj się pomóc!).
Zobacz testy kompilacji na końcu, aby zrozumieć każdą meta-funkcję szablonu.
Zapraszam do sugerują uproszczeń (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";
}
@ Jarod42 Tak masz rację, to nie jest problem. Nie było to jasne, więc zredagowałem pytanie: prawdziwa brakująca część to tak naprawdę zestaw przeciążeniowy, wspólny typ zwrotu: –