3

Betrachten Sie den folgenden Code, der eine Klasse zum Speichern von Funktionen erstellen.Perfekte Weiterleitung von Funktionen zum Erstellen einer Funktionsliste Klasse

// Function list class 
template <class... F> 
struct function_list 
{ 
    template <class... G> 
    constexpr function_list(G&&... g) noexcept 
    : _f{std::forward<G>(g)...} 
    { 
    } 
    std::tuple</* F... OR F&&... */> _f; 
}; 

// Function list maker 
template <class... F, class R = /* Can we compute the return type here? */> 
constexpr R make_function_list(F&&... f) 
{ 
    return function_list< 
     /* decltype(std::forward<F>(f))... 
     * OR F... 
     * OR F&&... 
     */>(std::forward<F>(f)...); 
} 

würde ich diese Funktionen wie perfekt weitergeleitet werden (unabhängig davon, ob sie Funktionszeiger, functors, Lambda-Ausdrücke ...). Aber ich verstehe nicht genau, was hinter std::forward und universellen Referenzen passiert. In dem obigen Code, habe ich drei Fragen:

  • Sollte _f vom Typ seiner std::tuple<F...> oder std::tuple<F&&...>
  • Ist es möglich, den Rückgabetyp R in der Vorlage Parameterliste abzuleiten (weil es zu tun (und warum?) manuell statt auto/decltype(auto) wäre hilfreich, zu verstehen, was los ist)
  • In dem Hersteller, was das function_list Template-Argument sein sollte: decltype(std::forward<F>(f)...), F oder F&&... (und warum)

Hinweis: Der Konstruktor function_list soll nicht direkt aufgerufen werden, stattdessen erledigt make_function_list die Aufgabe.

EDIT: Ist dieser Fall sicher, wenn die operator() von function_list (hier nicht dargestellten) guaranted ist nicht auf der gleichen Anweisung aufgerufen werden?

template <class... F> 
constexpr function_list<F...> make_function_list(F&&... f) 
{ 
    return function_list<F&&...>(std::forward<F>(f)...); 
} 
+0

Sie würden mit ziemlicher Sicherheit ** nicht ** wollen, dass sie 'F sein &&'. – SergeyA

+0

1) 'tuple ' 2) Sie würden 'R' in der Vorlagenliste nicht benötigen, Sie kennen bereits den Rückgabetyp' function_list '3)' F && 'weil es eine Weiterleitungsreferenz ist, die sowohl an rvalue als auch bindet lvalue refs (Sie sagten, Sie wollten eine perfekte Weiterleitung, also so würden Sie es bekommen) – AndyG

+1

Wenn Sie garantieren können, dass der Funktionsaufrufoperator innerhalb derselben Anweisung wie 'make_function_list' aufgerufen wird, können Sie' F && 'verwenden (Sie können drücke das mit einem ref-Qualifikationsmerkmal, '&&', auf 'operator()') –

Antwort

3

Aber ich verstehe nicht genau alle Art Abzug hinter std::forward und universellen Referenzen passiert.

Es ist ziemlich einfach, über ein Beispiel zu verstehen.

template <typename T> 
void f(T&&) 
{ 
    std::tuple<T>{}; // (0) 
    std::tuple<T&&>{}; // (1) 
} 

Im Fall von (0):

  • T als T für rvalues ​​abgeleitet wird
  • T als T& für lvalues ​​abgeleitet wird.

Im Fall von (1):

  • T als T&& für rvalues ​​abgeleitet wird
  • T wird als T& für lvalues ​​abgeleitet.

Wie Sie, der einzige Unterschied zwischen beiden zu sehen ist, wie rvalues ​​ abgeleitet werden.

std::forward angeht, ist es das, was sie tut:

template <typename T> 
void g(T&&); 

template <typename T> 
void f(T&& x) 
{ 
    g(x) // (0) 
    g(std::forward<T>(x)); // (1) 
} 

Im Fall von (0):

  • x ist immer ein lvalue.

Im Fall von (1):

  • x wird T&& gegossenem wenn T als T abgeleitet wird.

  • x bleibt ein lvalue andernfalls.

std::forward behält grundsätzlich die Art Kategorie von x, indem man, wie T abgeleitet.


Sollte Typ sein, der std::tuple<F...> oder std::tuple<F&&...> _f

Ich denke, dass in Ihrem Fall sollte es std::tuple<F...> sein, wie Sie entweder lvalue Referenzen oder Werte speichern möchten.

std::tuple<F&&...> würde speichern entweder lvalue Referenzen oder rvalue verweist -, die baumelnden Referenzen im Fall von Provisorien führen würde.


Ist es möglich, den Rückgabetyp R in der Vorlage Parameterliste

Ja, es ist nur function_list<F...> abzuleiten.

template <class... F, class R = function_list<F...>> 
constexpr R make_function_list(F&&... f) 
{ 
    return function_list<F...>(std::forward<F>(f)...); 
} 

Sie brauchen nicht einmal die R Template-Parameter.

template <class... F> 
constexpr function_list<F...> make_function_list(F&&... f) 
{ 
    return function_list<F...>(std::forward<F>(f)...); 
} 

In dem Hersteller, was die function_list Template-Argument sein sollte: decltype(std::forward<F>(f)...), F oder F&&...

function_list sollte F... als Template-Parameter aus den Gründen, nehmen an der aufgeführten Anfang dieser Antwort (dh Vermeiden von Dangling-Referenzen auf Provisorien).

Es sollte noch std::forward<F>(f)... als Argumente nehmen rvalues ​​ zu ermöglichen als solche (d.h. Bewegen rvalues ​​in function_list ‚s Tupel) weitergeleitet werden.

2

Wenn sie F&& sind, dann, wenn Sie eine temporäre zu make_function_list passieren, die zurück Klasse enthält eine tuple wird eine rvalue Bezug auf die vorübergehende zu make_function_list weitergegeben speichern.

In der nächsten Zeile ist es jetzt eine freie Referenz.

Dies scheint in den meisten Anwendungsfällen schlecht. Dies ist nicht wirklich schlecht in alle Anwendungsfälle; forward_as_tuple tut dies. Aber solche Anwendungsfälle sind nicht allgemeine Anwendungsfälle. Das Muster ist extrem spröde und gefährlich.

Wenn Sie im Allgemeinen eine T&& zurückgeben, möchten Sie sie als T zurückgeben. Dies kann eine Kopie des Objekts verursachen; aber die Alternative ist Danging-Reference-Hell.

Das gibt uns:

template<class... Fs> 
struct function_list { 
    template<class... Gs> 
    explicit constexpr function_list(Gs&&... gs) noexcept 
    : fs(std::forward<Gs>(gs)...) 
    {} 
    std::tuple<Fs...> fs; 
}; 
template<class... Fs, class R = function_list<Fs...>> 
constexpr R make_function_list(Fs&&... fs) { 
    return R(std::forward<Fs>(fs)...); 
} 

Auch function_list ‚s Ctor explicit, denn in der 1 Argument Fall machen es zu einem ziemlich gierig impliziten Konvertierungskonstruktor zufällt. Dies kann behoben werden, erfordert jedoch mehr Aufwand, als es wert ist.

operator() erfordert eine Instanz. Ein Typname ist keine Instanz.

0

Es hängt davon ab, was function_list ist. Grundsätzlich gibt es zwei Fälle:

  1. function_list ein temporärer Helfer, die nie die Anweisung überleben sollte es in erscheint Hier haben wir Hinweise auf Funktionen speichern und perfekt vorzuspulen jeden von ihnen bis zu dem Punkt des Aufrufs.

    würden bedeuten,
    template <class... F> 
    struct function_list 
    { 
        std::tuple<F&&...> f_; 
    
        // no need to make this template 
        constexpr function_list(F&&... f) noexcept 
         : f_{std::forward<F>(f)...} 
        {} 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) 
        { 
         return std::invoke(std::get<i>(f_), std::forward<A>(a)...); 
        } 
    }; 
    
  2. function_list ist ein Wrapper/Container-Objekt verwandt std::bind, in diesem Fall, dass Sie verfallene Kopien der Funktionen speichern wollen würden baumeln Referenzen und perfekte Weiterleitung in diesem Zusammenhang auf die Konstrukteurs-Weiterleitungsfunktionen zu vermeiden, ihre verfallenen Versionen in f_ und dann an der Stelle des Gesprächs die verfallene Funktionen mit Wertkategorie des function_list Imbuing selbst:

    template <class... F> 
    struct function_list 
    { 
        std::tuple<std::decay_t<F>...> f_; 
    
        template <typename... G> 
        constexpr function_list(G&&... g) 
         : f_{std::forward<G>(g)...} 
        {} 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) & 
        { 
         return std::invoke(std::get<i>(f_), std::forward<A>(a)...); 
        } 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) const& 
        { 
         return std::invoke(std::get<i>(f_), std::forward<A>(a)...); 
        } 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) && 
        { 
         return std::invoke(std::get<i>(std::move(f_)), std::forward<A>(a)...); 
        } 
    
        template <std::size_t i, typename... A> 
        decltype(auto) call_at(A&&... a) const&& 
        { 
         return std::invoke(std::get<i>(std::move(f_)), std::forward<A>(a)...); 
        } 
    }; 
    

    Wie bei std::bind, wenn Sie tatsächlich einen Verweis speichern möchten, müssen Sie mit std::reference_wrapper so explizit tun.

Construction ist in beiden Fällen gleich:

template <class... F> 
constexpr auto make_function_list(F&&... f) 
{ 
    return function_list<F...>(std::forward<F>(f)...); 
} 
Verwandte Themen