2017-05-23 3 views
1

Dies ist eine ähnliche Frage zu Using std::forward on sub fields, aber die Antwort scheint nicht in meinem Fall zutreffen.Mit std :: forward bei Casted Argumenten

Consider this code:

template<class Base, class F> 
void visit(Base&&, const F&) { 
    throw std::bad_cast(); 
} 

template<class Derived, class... Rest, class Base, class F> 
void visit(Base&& base, const F& f) { 
    if (auto *as_derived = dynamic_cast<Derived *>(&base)) { 
     return f(std::forward<Base>(*as_derived)); 
    } else { 
     return visit<Rest...>(std::forward<Base>(base), f); 
    } 
} 

Mein Ziel ist es, dass die folgende Testfallarbeit:

struct Animal { 
    virtual ~Animal() {} 
}; 
struct Cat : Animal { 
    void speak() & { puts("meow"); } 
    void yowl() && { puts("MEOW!"); } 
}; 
struct Dog : Animal { 
    void speak() & { puts("woof"); } 
    void yowl() && { puts("WOOF!"); } 
}; 

int main() { 
    Animal *a = new Cat(); 
    Animal *b = new Dog(); 
    visit<Cat, Dog>(*a, [](auto&& a){ std::forward<decltype(a)>(a).speak(); }); 
    visit<Cat, Dog>(*b, [](auto&& a){ std::forward<decltype(a)>(a).speak(); }); 
    visit<Cat, Dog>(std::move(*a), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); }); 
    visit<Cat, Dog>(std::move(*b), [](auto&& a){ std::forward<decltype(a)>(a).yowl(); }); 
} 

gewünschte Ausgabe: "Meow" "Miau" "Wuff" "SCHUSS!". Beachten Sie, dass die Funktionen speak und yowl nicht virtuell sind; Dies ist in meinem ursprünglichen Code erforderlich, da es sich tatsächlich um Vorlagen handelt und Vorlagen nicht virtuell sein können.

Das Problem mit diesem Code wie hier geschrieben ist, dass std::forward<Base>(*as_derived) nicht nur die ref-Qualifiers und const-Qualifier auf *as_derived ändern, um perfekte Weiterleitung zu ermöglichen; es wirft tatsächlich den Typ zurück zu Base&, nerfing den ganzen Punkt von visit!

Gibt es eine Standard-Library-Funktion, die das tut, was ich will std::forward zu tun - nämlich, ändern Sie die ref-Qualifier und const-Qualifizierer auf *as_derived diejenigen entsprechen, die von std::forward<Base> perfekt-vorne abgeleitet sein würde?

Wenn es keine Standardbibliotheksfunktion gibt, wie könnte ich eine Funktion für die "perfekte Weiterleitung eines Kindtyps" für meinen eigenen Gebrauch schreiben?

Die obige Wandbox-Verbindung enthält etwas, das für diesen Testfall "funktioniert", aber es behält die Konstanz nicht und es sieht überhaupt nicht elegant aus.

Antwort

4

Dafür gibt es im Standard nichts. Aber es ist nicht schwer zu schreiben. Nur nervig. Was Sie tun müssen, ist ein Merkmal schreiben, dass Sie den Typ in forward passieren gibt - im Grunde Sie die cv-Qualifikation und Referenz-ness von Derived, was Base ist, übereinstimmen soll und dann diese Art in forward passieren:

return f(std::forward<match_ref_t<Base, Derived>>(*as_derived)); 

Eine einfache Implementierung, die fast sicher prägnanter gemacht werden kann, ist nur:

template <class From, class To> 
struct match_ref { 
    using type = To; 
}; 

template <class From, class To> 
using match_ref_t = typename match_ref<From, To>::type; 

template <class From, class To> 
struct match_ref<From&, To> { 
    using type = match_ref_t<From, To>&; 
}; 

template <class From, class To> 
struct match_ref<From&&, To> { 
    using type = match_ref_t<From, To>&&; 
}; 

template <class From, class To> 
struct match_ref<From const, To> { 
    using type = match_ref_t<From, To> const; 
}; 

template <class From, class To> 
struct match_ref<From volatile, To> { 
    using type = match_ref_t<From, To> volatile; 
}; 

template <class From, class To> 
struct match_ref<From const volatile, To> { 
    using type = match_ref_t<From, To> const volatile; 
}; 

Oder ich denke:

template <class Check, template <class> class F, class T> 
using maybe_apply = std::conditional_t<Check::value, F<T>, T>; 

template <class From, class To> 
struct match_ref { 
    using non_ref = std::remove_reference_t<From>; 
    using to_cv = maybe_apply<std::is_const<non_ref>, std::add_const_t, 
      maybe_apply<std::is_volatile<non_ref>, std::add_volatile_t, 
      To>>; 

    using type = std::conditional_t< 
     std::is_lvalue_reference<From>::value, 
     to_cv&, 
     std::conditional_t< 
      std::is_rvalue_reference<From>::value, 
      to_cv&&, 
      to_cv> 
     >; 
}; 

template <class From, class To> 
using match_ref_t = typename match_ref<From, To>::type; 
2

Vorwärts ist nur ein bedingter Zug.

template<bool b> 
struct move_if_t{ 
    template<class T> 
    T&& operator()(T&t)const{ return std::move(t); } 
}; 
template<> 
struct move_if_t<false>{ 
    template<class T> 
    T& operator()(T&t)const{ return t; } 
}; 
template<bool b, class T> 
decltype(auto) move_if(T& t){ 
    return move_if_t<b>{}(t); 
} 

Jetzt bekommen wir

template<class Derived, class... Rest, class Base, class F> 
void visit(Base&& base, const F& f) { 
    if (auto *as_derived = dynamic_cast<Derived *>(&base)) { 
    return f(move_if<!std::is_lvalue_reference<Base>{}>(*as_derived)); 
    } else { 
    return visit<Rest...>(std::forward<Base>(base), f); 
    } 
} 
+0

Das ist viel schöner als die Auflistung aller cv-ref Sachen. – Barry

0

Yakk Antwort ist bewundernswert präzise und scheint zu funktionieren, aber ich landete in der Praxis mit Barrys Antwort gehen, weil ich fand, dass match_cvref_t viel einfacher war, an der Vernunft über als irgendeine der Alternativen. Außerdem musste ich in meinem speziellen Fall unbedingt auf match_cvref_t verweisen, um den eigentlichen Casting-Vorgang korrekt auszuführen.Also:

template<class Base, class F> 
void visit(Base&&, const F&) { 
    throw std::bad_cast(); 
} 

template<class DerivedClass, class... Rest, class Base, class F> 
void visit(Base&& base, const F& f) { 
    if (typeid(base) == typeid(DerivedClass)) { 
     using Derived = match_cvref_t<Base, DerivedClass>; 
     return f(std::forward<Derived>(static_cast<Derived&&>(base))); 
    } else { 
     return visit<Rest...>(std::forward<Base>(base), f); 
    } 
} 

Ich habe es geschafft, jedes match_cvref_t bis

template<class From, class To> 
using match_cvref_t = match_ref_t< 
    From, 
    match_cv_t< 
     std::remove_reference_t<From>, 
     std::remove_reference_t<To> 
    > 
>; 

wo match_cv_t und match_ref_t nehmen etwa 5 Zeilen Code zu schrumpfen.

Verwandte Themen