2017-09-07 31 views
7

Zastanawiam się, jak powinny wyglądać konwersje zwracające typu: std::visit.Wizytacja wariantu i typ_typowy

Kontekst jest następujący: Mam obiekt wariantu i chcę zastosować (poprzez std::visit) różne funkcje w zależności od jego rodzaju podstawowego. Wynik każdej funkcji może mieć inny typ, ale wtedy chciałbym std :: visit, aby spakować go w typie wariantu.

Pseudo-kod:

mam:

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

chcę:

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> 

Teraz, według cppreference, rodzaj powrotu std :: wizyty jest „Wartość zwracane przez wybrane wywołanie gościa, konwertowane na wspólny typ wszystkich możliwych wyrażeń std :: invoke " Ale to, co typowy oznacza znaczy, nie jest określone. Czy to jest std::common_type? W tym przypadku to nie działa z 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*a.i}; 
    } 
    auto 
    operator()(B b) -> B { 
     return {3*b.j}; 
    } 
}; 


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] 

} 

Co należy zrobić, aby wyrazić to rozpakować - stosować wizytację - zapakować wzór?

Uwagi:

1) specjalizująca std::common_type<A(*)(Ts...),B(*)(Ts...)> nie będzie go wyciąć. Byłoby to możliwe, ale polegać na konkretnym szczególe implementacji std :: lib. Plus to nie działa w przypadku wielu odwiedzin.

2) Podany przeze mnie przykład jest naprawdę zredukowany do absolutnego minimum, ale trzeba sobie wyobrazić, że mechanizm odwiedzin, który chcę zapewnić, znajduje się po stronie biblioteki, a odwiedzający są po stronie klienta i mogą być arbitralni skomplikowane: nieznana liczba i typy argumentów, nieznane typy zwrotu. Biblioteka powinna po prostu zapewnić odwiedziny i predefiniowany zestaw specjalizacji std::common_type, które będą używane do zwracania typów odwiedzin. Tak na przykład, definiowanie

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

a następnie zastosowanie std::visit do f nie jest realną opcją: od strony biblioteki, nie mogę predefine tego rodzaju lambda nie znając „zapakowany” typ zwracany. [Głównym problemem jest to, że widzę żadnego sposobu zadawania języka dla std::common_type konkretnego zestawu przeciążenia]

+0

@ 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: –

Odpowiedz

4

można stworzyć swój własny visit warstwę, coś takiego:

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

+0

Hum ciekawe, nie myślałem o tym. Działa na pojedyncze odwiedziny, ale nie widzę w tej chwili, jak rozszerzyć go na wielokrotne odwiedziny. –

+0

Aby rozwinąć do wielu, musisz również mieć typ common_ dla 'variant i C', który naprawdę nie jest naprawdę skalowalny. Rozwiązanie Yakka jest bardziej skalowalne, ale wciąż masz trochę pracy (usuń zduplikowany typ, ...). – Jarod42

+0

Pracuję nad generowaniem krzyżowego produktu zestawu przeciążenia argumentów i jest to dość skomplikowane ... Kiedy skończę, rozszerzenie twojego rozwiązania jest banalne. Opublikuję to tutaj. –

2

Twoim głównym problemem jest fakt, że std::visit wyraźnie wymaga, aby wszystkie typy wywołań różnych wywołań dostarczonych przez odwiedzającego były tego samego typu, a specjalizowanie się w std::common_type nic nie naprawi. Deskryptor "Common Type", który wyciągnąłeś ze Standardu, jest oznaczany potocznie, a nie jako typ literowy.

Innymi słowy, użytkownik musiprzybrać formę

struct Visitor { 
    using some_type = /*...*/; 

    some_type operator()(A const& a); 
    some_type operator()(B const& b); 

}; 

Na szczęście, jest to problem, który rozwiązuje się. Ponieważ istnieje już typ wspólny, który można przypisać z tego rodzaju permutacji na przechowywanej wartości: variant, którą opisałeś w pierwszej kolejności.

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

To powinno się skompilować i spowodować zachowanie, którego się spodziewasz.

+0

@ Jarod42 Naprawiono. Nie złapałem tego. – Xirema

+0

Jednak problem wspomniany w uwadze # 2 pozostaje nadal: w ogólnym przypadku nie chcę, aby mój klient zwrócił ten wariant, i nie mogę zawijać ich funkcji, ponieważ nie znam typów zwracanych –

+1

@ Bérenger Musisz dokładniej określić, dlaczego nie chcesz zwrócić obiektu 'variant'. Ponieważ rozwiązanie twojego problemu różni się w zależności od dokładnego rozumowania, że ​​API zwracający 'wariant <...>' jest zły dla ciebie. – Xirema

3
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>...> 
{}; 

to daje wynik zastosowania F do wiązki typów postaci wiązki typów.

Dodaj do transkrypcji od wariantu, może powielać usunięcie, owijkę, która pobiera F a variant<Ts...> i tworzy F2 który wywołuje F i zawraca wspomniany wariant, po czym przechodzi do visitF2 i jesteśmy hakf sposób tam.

Druga połowa to obsługa wielu wariantów. Aby to osiągnąć, musimy pobrać produkt wielu pakietów typów, uzyskać wynik wywołania wszystkich z nich i powiązać je.

0

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"; 
}