2017-09-07 5 views
7

Ich frage mich, wie std::visit Return-Typ-Konvertierungen funktionieren sollen.Variant visitation und common_type

Der Kontext ist folgender: Ich habe ein variantes Objekt und ich möchte (über std::visit) verschiedene Funktionen abhängig von seinem zugrunde liegenden Typ anwenden. Das Ergebnis jeder Funktion kann einen anderen Typ haben, aber dann möchte ich std :: visit, um es in einem Variant-Typ zu packen.

Pseudo-Code:

ich habe:

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

Ich möchte:

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> 

nun nach cppreference, der Rückgabetyp von std :: Besuch ist „Der Wert zurückgegeben durch den ausgewählten Aufruf des Besuchers, umgewandelt in den allgemeinen Typ aller möglichen std :: invoke Ausdrücke " Aber welcher gemeinsamer Typ bedeutet, wird nicht spezifiziert. Ist es std::common_type? In diesem Fall funktioniert es nicht mit 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] 

} 

Was soll ich tun, um dieses auspacken auszudrücken - Visitation gelten - umpacken Muster?

Hinweise:

1) std::common_type<A(*)(Ts...),B(*)(Ts...)> Spezialisierung wird es nicht schneiden. Dies würde den Trick machen, aber sich auf ein bestimmtes std :: lib-Implementierungsdetail verlassen. Plus es funktioniert nicht für Multi-Besuch.

2) Das Beispiel, das ich angegeben habe, ist wirklich auf das Nötigste reduziert, aber Sie müssen sich vorstellen, dass der Besuchsmechanismus auf der Bibliotheksseite ist und die Besucher auf der Clientseite sind und beliebig sein können kompliziert: unbekannte Anzahl und Typen von Argumenten, unbekannte Rückgabetypen. Die Bibliothek sollte nur einen Besuch und eine vordefinierte Gruppe von std::common_type Spezialisierungen bereitstellen, die für Besuchsrückgabetypen verwendet werden. So zum Beispiel der Definition

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

und dann die Anwendung std::visit-f ist keine gangbarer Weg: aus der Bibliothek Seite, kann ich diese Art von Lambda nicht vordefinieren, ohne den „verpackt“ Rückgabetyp zu kennen. [Das Hauptproblem ist, dass ich keine Möglichkeit zu fragen, die Sprache für die std::common_type ein bestimmtes Überlast-Set sehen]

+0

@ Jarod42 Ja, Sie haben Recht, das ist nicht das Problem ist. Ich war nicht klar, also habe ich die Frage bearbeitet: der wirkliche fehlende Teil ist wirklich der Überlastsatz gemeinsamer Rückgabetyp –

Antwort

4

Sie können Ihre eigene visit Schicht, so etwas wie erstellen:

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 interessant, ich habe nicht darüber nachgedacht. Es funktioniert für Einzelbesuche, aber ich sehe derzeit nicht, wie man es auf mehrere Besuche ausdehnen kann. –

+0

Um auf mehrere zu erweitern, müssen Sie auch common_type für 'Variante und C' haben, die in der Tat nicht wirklich skalierbar ist. Yakks Lösung ist skalierbarer, aber Sie haben noch etwas Arbeit (entfernen Sie doppelten Typ, ...). – Jarod42

+0

Ich arbeite an der Generierung des Kreuzprodukts von Argument Overload Set und es ist ziemlich komplex ... Sobald ich fertig bin, ist die Erweiterung Ihrer Lösung trivial. Ich werde es hier posten. –

2

Ihr Hauptproblem ist die Tatsache, dass std::visit ausdrücklich fordert alle Rückgabetypen der verschiedenen Aufrufe durch den Besucher des gleichen Typs zur Verfügung gestellt werden, und spezialisiert std::common_type tut nichts, um das zu beheben. Der Deskriptor "Common Type", den Sie aus dem Standard gezogen haben, ist umgangssprachlich gemeint, nicht als Literaltyp.

Mit anderen Worten muss die Besucher

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

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

}; 

Zum Glück die Form nehmen, ist dies ein Problem, das sich löst. Weil es bereits einen gemeinsamen Typ gibt, der aus dieser Art von Permutation auf den gespeicherten Wert zugewiesen werden kann: die variant, die Sie an erster Stelle beschrieben haben.

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

Dies sollte kompilieren und ergeben das Verhalten, das Sie erwarten.

+0

@ Jarod42 Korrigiert. Ich habe das nicht verstanden. – Xirema

+0

Das Problem, das in Anmerkung 2 erwähnt wird, bleibt jedoch bestehen: Im Allgemeinen möchte ich nicht, dass mein Client die Variante zurückgibt, und ich kann ihre Funktionen nicht umbrechen, weil ich die Rückgabetypen –

+1

@ Bérenger nicht kenne Sie müssen genauer angeben, warum Sie ein 'variant'-Objekt nicht zurückgeben wollen. Denn die Lösung für Ihr Problem unterscheidet sich je nach der genauen Argumentation, dass eine API, die die Variante <...> zurückgibt, schlecht für Sie ist. – 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>...> 
{}; 

das gibt Ihnen ein das Ergebnis der F zu einem Bündel von Typen als ein Bündel von Arten der Anwendung.

In-Variante von transkribieren, vielleicht Entfernung duplizieren, einen Wrapper, der F und ein variant<Ts...> und schafft eine F2, die dort F nennt und kehrt die Variante, dann geht die F2-visit, und wir sind hakf Weg nimmt.

Die andere Hälfte soll mehrere Varianten behandeln. Um das zu erreichen, müssen wir ein Produkt aus mehreren Typenbündeln nehmen, das Aufrufergebnis von allen erhalten und bündeln.

0

Meine Lösung für mehrere Besuche. Danke an Jarod42, dass er mir den Weg gezeigt hat.

Live Demo

Das Hauptproblem ist das Kreuzprodukt aller möglichen Anrufen zu einer Überlastung Satz zu erzeugen. Diese Antwort adressiert nicht das Problem einer generischen Konvertierung von Rückgabetypen, ich habe gerade eine Ad-hoc-Spezialisierung von std::common_type gemacht (ich denke, das ist genug, um meine Bedürfnisse zu erfüllen, aber fühlen Sie sich frei, beizutragen!).

Siehe Compile-Time-Tests am Ende, um jede Vorlage Meta-Funktion zu verstehen.

Fühlen Sie sich frei Vereinfachungen vorschlagen (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"; 
}