Visit Option and common_type

I am wondering how inverse conversions like std::visit should work.

The context is as follows: I have a variant of the object, and I want to apply (via std::visit ) various functions depending on its base type. The result of each function may have a different type, but then I would like std :: visit to pack it into a variant type.

Pseudo Code:

I have:

 variant<A,B> obj f(A) -> A f(B) -> B 

I want:

 if obj is of type A => apply f(A) => resA of type A => pack it in variant<A,B> if obj is of type B => apply f(B) => resB of type B => pack it in variant<A,B> 

Now, according to cppreference , the return type std :: visit is the value returned by the visitor selected by the call, converted to the common type of all possible expressions std :: invoke "But what common type is not specified. Is it std::common_type ? In this case, it works with gcc 7.2:

 #include <variant> #include <iostream> #include <type_traits> struct A { int i; }; struct B { int j; }; // the standard allows to specialize std::common_type 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>; }; } struct Functor { auto operator()(A a) -> A { return {2*ai}; } auto operator()(B b) -> B { return {3*bj}; } }; int main() { std::variant<A,B> var = A{42}; auto res = std::visit( Functor() , var ); // error: invalid conversion from 'std::__success_type<B>::type (*)(Functor&&, std::variant<A, B>&) {aka B (*)(Functor&&, std::variant<A, B>&)}' to 'A (*)(Functor&&, std::variant<A, B>&)' [-fpermissive] } 

What to do to express this unboxing - apply visit - repack template?

Notes:

1) The specialization std::common_type<A(*)(Ts...),B(*)(Ts...)> will not abbreviate it. This might do the trick, but rely on the specific implementation detail of std :: lib. In addition, it does not work for multiple visits.

2) The example that I gave really reduces to the minimum minimum, but you have to imagine that the visiting mechanism that I want to provide is on the library side, and the visitors are on the client side and can be arbitrary: an unknown number and types of arguments, unknown return types. The library should simply provide a visit and a predefined set of std::common_type specializations to be used for return types. So, for example, the definition

 auto f = [](auto x) -> variant<A,B> { return Functor()(x); }; 

and then using std::visit to f not a viable option: on the library side, I cannot predetermine this kind of lambda without knowing the "packed" return type. [The main problem is that I don’t see a way to request a language for std::common_type specific overload set]

+7
c ++ variant c ++ 17
source share
4 answers

You can create your own visit layer, for example:

 template <typename Visitor, typename ... Ts> decltype(auto) my_visit(Visitor&& vis, const std::variant<Ts...>& var) { return std::visit([&](auto&& e) -> std::common_type_t<decltype(vis(std::declval<Ts>()))...> { return vis(e); }, var); } 

Demo

+4
source share
 template<class...>struct types{using type=types;}; template<class F, class Types> struct results; template<class F, class...Ts> struct results<F, types<Ts...>>: types<std::invoke_result_t<F,Ts>...> {}; 

this gives you the result of applying F to a type set as a type set.

Add the transcription option to the variant, possibly duplicate deletion, a shell that takes F and variant<Ts...> , and creates F2 , which calls F and returns the specified option, then passes F2 to visit , and we hakf there.

The other half is for handling multiple options. To get this, we need to take the cross product of several sets of types, get the result of calling all of them and link it.

+3
source share

Your main problem is that std::visit explicitly requires that all return types of the various calls provided by the visitor be of the same type, and the specialization std::common_type does nothing to fix it. The "General Type" descriptor that you pulled out of the Standard is designated as colloquial, not literal.

In other words, the visitor must have the form

 struct Visitor { using some_type = /*...*/; some_type operator()(A const& a); some_type operator()(B const& b); }; 

Fortunately, this is a problem that solves itself. Because there already exists a general type that can be assigned from this kind of permutation according to the stored value: variant , which you described first.

 struct Functor { std::variant<A,B> operator()(A const& a) const { return A{2*ai}; } std::variant<A,B> operator()(B const& b) const { return B{3*bj}; } }; 

This should compile and give the behavior you expect.

+2
source share

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"; } 
0
source share

All Articles