2016-06-13 10 views
3

Ich habe in der Suche nach einem static_if für mein C++ Projekt zu schreiben, und ich auf das folgende Stück Code gestolpert:std vorwärts und Betreiber ::()

#include <iostream> 
using namespace std; 

namespace static_if_detail { 

struct identity { 
    template<typename T> 
    T operator()(T&& x) const { 
     return std::forward<T>(x); 
    } 
}; 

template<bool Cond> 
struct statement { 
    template<typename F> 
    void then(const F& f){ 
     f(identity()); 
    } 

    template<typename F> 
    void else_(const F&){} 
}; 

template<> 
struct statement<false> { 
    template<typename F> 
    void then(const F&){} 

    template<typename F> 
    void else_(const F& f){ 
     f(identity()); 
    } 
}; 

} //end of namespace static_if_detail 

template<bool Cond, typename F> 
static_if_detail::statement<Cond> static_if(F const& f){ 
    static_if_detail::statement<Cond> if_; 
    if_.then(f); 
    return if_; 
} 

template<typename T> 
void decrement_kindof(T& value){ 
    static_if<std::is_same<std::string, T>::value>([&](auto f){ 
     f(value).pop_back(); 
    }).else_([&](auto f){ 
     --f(value); 
    }); 
} 


int main() { 
    // your code goes here 
    std::string myString{"Hello world"}; 
    decrement_kindof(myString); 
    std::cout << myString << std::endl; 
    return 0; 
} 

Alles macht Sinn für mich, außer zum einen: der überladene Operator() in struct identity. Es nimmt eine rhs vom Typ T auf, genannt x, cool und all. Aber wenn identity aufgerufen wird, wird nichts tatsächlich in Identität übergeben.

template<typename F> 
void then(const F& f){ 
    f(identity()); 
} 

Oben, f ruft die Identität auf, übergibt jedoch nichts an die Identität. Die Identität gibt jedoch die weitergeleiteten Argumente (in meinem Fall eine std :: string) zurück und gibt das hinterste Zeichen der Zeichenkette aus. Wie gibt die Identität ein weitergeleitetes Argument zurück, wenn selbst keine Argumente zum Weiterleiten übergeben wurden?

+1

Für mich sieht es so aus, als wäre 'f' eine Funktion und wird mit einem einzigen Argument vom Typ' identity' aufgerufen. Mit anderen Worten, "identity()" konstruiert eine Instanz des Typs "identity", der an die Funktion "f" übergeben wird. –

+1

[This] (http://ideone.com/p8SlI9) ist, wie es aussehen würde, um ein ähnliches Objekt zu konstruieren und aufzurufen. –

Antwort

4

f ruft nicht auf identity - f wird mit einer Instanz von identity aufgerufen. Ein Spaziergang durch die beiden Fälle hier:

static_if<std::is_same<std::string, T>::value>([&](auto f){ 
    f(value).pop_back(); 
}).else_([&](auto f){ 
    --f(value); 
}); 

Wenn T ist std::string, dann instanziiert wir ein statement<true> deren then() die übergebenen in Funktion mit einer Instanz von identity aufruft. Das Argument zum ersten Lambda, f, wird vom Typ identity sein - also f(value) ist wirklich nur value und wir tun value.pop_back().

Wenn T nicht std::string ist, dann instanziiert wir ein statement<false> deren then() tut nichts, und deren else_() ruft das Lambda mit einer Instanz von identity. Wieder f(value) ist nur value und wir tun --value.


Dies ist eine wirklich verwirrend Implementierung von static_if, da f im Lambda immer ein identity ist. Es ist notwendig zu tun, weil wir value nicht direkt verwenden können (value.pop_back() kann nicht schreiben, da es dort keinen abhängigen Namen gibt, so wird der Compiler glücklicherweise feststellen, dass es für Integer schlecht gebildet ist), so wickeln wir nur alle Verwendungen von value in ein abhängiges Funktionsobjekt, das diese Instanziierung verzögert (f(value) ist abhängig von f, kann also nicht instanziiert werden, bis f bereitgestellt wird - was nicht passieren wird, wenn die Funktion nicht aufgerufen wird).

Es wäre besser, es so zu implementieren, dass Sie tatsächlich die Argumente an das Lambda übergeben.

+1

Oder zumindest "id" oder "var" oder "safe" statt "f" nennen. – Yakk

+0

Diese Antwort klingt nach rechts, +1, aber es lässt immer noch den Design-Level-Zweck von "Identität" ein Geheimnis. Warum kann z.B. die Zahl "42" anstelle einer Instanz von "Identität" übergeben werden? –

+0

@ Cheersandthth.-Alf Eine Erklärung zu diesem Effekt hinzugefügt. – Barry

0

Nehmen wir den Fall, wo Condtrue in static_if ist, daher wird die primäre Template-Klasse verwendet ...

template<bool Cond> 
struct statement { 
    template<typename F> 
    void then(const F& f){ 
     f(identity()); 
    } 

    template<typename F> 
    void else_(const F&){} 
}; 

Recall, dass Ihre Berufung Funktion:

static_if<std::is_same<std::string, T>::value> 
(
    [&](auto f){ //This is the lamda passed, it's a generic lambda 
     f(value).pop_back(); 
    } 
).else_(
    [&](auto f){ 
     --f(value); 
    } 
); 

In der nachstehenden Anwendung Funktion F ist eine Art dieser generic Lambda(Bedeutung, können Sie anrufen f mit einem beliebigen Typ)

template<typename F> 
void then(const F& f){ 
    f(identity()); 
} 

identity() erstellt ein Objekt vom Typ identity, das dann als Argument übergeben wird, an Anruf Ihr generisches Lambda.

[&](auto f){ //This is the lamda passed, it's a generic lambda 
     f(value).pop_back(); 
    } 

aber Recall, f ist ein Objekt vom Typ identity und hat einen Anruf templated () Operator, die im Grunde das Objekt an sie übergeben zurückgibt.


Also, gehen wir wie folgt aus:

void decrement_kindof(std::string& value){ 
    static_if<std::is_same<std::string, std::string>::value>([&](auto f){ 
     f(value).pop_back(); 
    }).else_([&](auto f){ 
     --f(value); 
    }); 
}); 

Reduziert auf:

void decrement_kindof(std::string& value){ 
    static_if<true>([&](auto f){ 
     f(value).pop_back(); 
    }).else_([&](auto f){ 
     --f(value); 
    }); 
}); 

Reduziert auf:

void decrement_kindof(std::string& value){ 
    static_if<true>(
     [&](identity ident){ 
      auto&& x = ident(value); //x is std::string() 
      x.pop_back(); 
     } (identity()) //<-- the lambda is called 

    ).else_(
     [&](auto f){ //This is not evaluated, because it's not called by the primary template of `statement` 
      --f(value); 
     } 
    ); 
}); 
1
template<typename F> 
void then(const F& f){ 
    f(identity()); 
} 

Ist mo wieder lesbar als

template<typename F> 
void then(const F& f){ 
    f(identity{}); 
} 

sie ein Identitätsobjekt zu konstruieren, nicht einen Aufruf.

Der Trick hier ist, dass die nicht abhängigen Teile einer Template-Funktion gültig sein müssen, auch wenn die Funktion nie instanziiert wird.

So ist value.pop_back() nie innerhalb der Lambda gültig, wenn der Wert eine ganze Zahl ist.

Indem wir identity{} zu genau einem der then oder else Fälle übergeben, können wir dieses Problem vermeiden.

Die Anweisung f(value) erzeugt einen abhängigen Typ. Es muss also nur gültig sein, wenn die Vorlage operator() des Lambda tatsächlich instanziiert wird (es muss auch eine Möglichkeit geben, f, die es gültig macht, aber das ist ein Eckfall).

Da wir nur den Pfad instanziieren, den uns die Bedingung sagt, kann die f(value) fast beliebig verwendet werden, solange sie in der genommenen Verzweigung gültig ist.

Ich hätte f einen besseren Namen, wie safe oder guard oder var oder magic statt f genannt. Die Verwendung von zwei unterschiedlichen Kontexten in dem knappen Code trägt zur Verwirrung bei.

Verwandte Themen