5

Ich untersuche ein Stroustroups Buch "C++ Programmierung 4. Edition". Und ich versuche seinem Beispiel im Matrix-Design zu folgen.Brauchen Sie Hilfe, um Template-Funktion mit komplexen typename Parameter zu verstehen

Seine Matrix-Klasse hängt stark von Vorlagen ab und ich versuche mein Bestes, um sie herauszufinden. Hier ist eines der Hilfsklassen für diese Matrix

A Matrix_slice der Teil der Matrix-Implementierung ist, dass die Karten eine Reihe von Indizes an die Stelle eines Elements. Es nutzt die Idee allgemeiner Scheiben (§40.5.6)

template<size_t N> 
struct Matrix_slice { 
    Matrix_slice() = default; // an empty matrix: no elements 
    Matrix_slice(size_t s, initializer_list<size_t> exts); // extents 
    Matrix_slice(size_t s, initializer_list<size_t> exts, initializer_list<siz e_t> strs);// extents and strides 
    template<typename... Dims> // N extents 
    Matrix_slice(Dims... dims); 


    template<typename... Dims, 
    typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
    size_t operator()(Dims... dims) const; // calculate index from a set of subscripts 

    size_t size; // total number of elements 
    size_t start; // star ting offset 
    array<size_t,N> extents; // number of elements in each dimension 
    array<size_t,N> strides; // offsets between elements in each dimension 
}; 
I 

Hier sind die Linien, die das Thema meiner Frage aufbauen:

template<typename... Dims, 
     typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
     size_t operator()(Dims... dims) const; // calculate index from a set of subscripts 

früher in dem Buch er beschreibt, wie Enable_if und All() implementiert werden:

template<bool B,typename T> 
using Enable_if = typename std::enable_if<B, T>::type; 


constexpr bool All(){ 
    return true; 
} 
template<typename...Args> 
constexpr bool All(bool b, Args... args) 
{ 
    return b && All(args...); 
} 

Ich habe genug Bildung verstehen, wie sie funktionieren bereits und bei seiner Enable_if Implementierung ich auf der Suche als auch Cabrio-Funktion ableiten kann:

template<typename From,typename To> 
bool Convertible(){ 
    //I think that it looks like that, but I haven't found 
    //this one in the book, so I might be wrong 
    return std::is_convertible<From, To>::value; 
} 

So kann ich die Bausteine ​​dieser Vorlage Funktionsdeklaration under aber ich bin verwirrt, wenn sie versuchen zu verstehen, wie sie alt arbeiten. Ich hoffe, dass Sie

template<typename... Dims, 
//so here we accept the fact that we can have multiple arguments like (1,2,3,4) 

     typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
     //Evaluating and expanding from inside out my guess will be 
     //for example if Dims = 1,2,3,4,5 
     //Convertible<Dims,size_t>()... = Convertible<1,2,3,4,5,size_t>() = 
     //= Convertible<typeof(1),size_t>(),Convertible<typeof(2),size_t>(),Convertible<typeof(3),size_t>(),... 
     //= true,true,true,true,true 

     //All() is thus expanded to All(true,true,true,true,true)    
     //=true; 

     //Enable_if<true> 
     //here is point of confusion. Enable_if takes two tamplate arguments, 
     //Enable_if<bool B,typename T> 
     //but here it only takes bool 

     //typename = Enable_if(...) this one is also confusing 

     size_t operator()(Dims... dims) const; // calculate index from a set of subscripts 

helfen könnte Also, was tun wir am Ende bekommen? Dieses Konstrukt

template<typename ...Dims,typename = Enable_if<true>> 
size_t operator()(Dims... dims) const; 

Die Fragen sind:

  1. wir die zweite Vorlageargument für Enable_if
  2. Warum haben wir Zuordnung ('=') für einen Typname
  3. Sie nicht brauchen Was bekommen wir am Ende?

Update: Sie den Code in dem gleichen Buch überprüfen, die ich Referenzierung hier The C++ Programming Language 4th edition auf Seite 841 (Matrix Design)

+0

Dies kann kein echter Code sein. 'Enable_if () ...)>' löst einen Compilerfehler aus, da das Template-Argument nur ein zur Kompilierzeit bekannter Ausdruck sein kann. Ein Rückgabewert von 'All' ist nicht. – SergeyA

+0

@SergeyA Ich nehme an, er hat gerade einen 'constexpr' vor den Funktionsdefinitionen vermisst. Ich bin mir ziemlich sicher, dass es funktionieren wird. – SirGuy

+0

@GuyGreer, OP könnte. Aber als was sonst fehlt OP? ;) – SergeyA

Antwort

1

Die fehlenden constexpr s ungeachtet std::enable_if ist eine Vorlage, nimmt zwei Parameter, aber der zweite ist standardmäßig void. Es macht Sinn, wenn Sie einen schnellen Alias ​​erstellen, um diese Konvention einzuhalten.

Daher sollte der Alias ​​definiert werden als:

template <bool b, class T = void> 
    using Enable_if = typename std::enable_if<b, T>::type; 

ich keinen Einblick in, ob diese Standardparameter in dem Buch vorhanden sind oder nicht, nur, dass dies, dass Problem beheben wird.

Die Zuordnung eines Typs nennt sich type alias und macht, was es auf dem Zinn sagt, wenn Sie auf den Alias ​​verweisen, beziehen Sie sich eigentlich auf was es Aliase. In diesem Fall bedeutet dies, dass der Compiler Enable_if<b>typename std::enable_if<b, void>::type für Sie manuell erweitert, so dass Sie alle zusätzlichen Schreibvorgänge sparen.

Am Ende erhalten Sie eine Funktion, die nur aufgerufen werden kann, wenn jeder übergebene Parameter in eine std::size_t konvertierbar ist. Dadurch können Überladungen von Funktionen ignoriert werden, wenn bestimmte Bedingungen nicht erfüllt sind. Dies ist eher eine leistungsfähige Technik als nur die Auswahl von passenden Typen zur Auswahl der zu rufenden Funktion. Der Link für std::enable_if hat mehr Informationen darüber, warum Sie das tun möchten, aber ich warne Anfänger, dass dieses Thema etwas berauschend wird.

+0

Danke, aber immer noch Es ist nicht 2 weitere Fragen im Thema beantworten – Antiusninja

4

Dies ist grundlegende SFINAE. Sie können es zum Beispiel here lesen.

für die Antworten, verwende ich std::enable_if_t hier anstelle des EnableIf in dem Buch, aber die beiden sind identisch:

  1. Wie @GuyGreer in der Antwort erwähnt, die zweite Template-Parameter von ist standardmäßig void.

  2. Der Code kann als eine „normale“ Funktionsschablone Definition

    template<typename ...Dims, typename some_unused_type = enable_if_t<true> > 
    size_t operator()(Dims... dims) const; 
    

    Mit dem = der Parameter gelesen werden some_unused_type auf der rechten Seite auf die Art vorbelegt. Und da man den Typ some_unused_type nicht explizit verwendet, muss man auch keinen Namen vergeben und einfach leer lassen.

    Dies ist der übliche Ansatz in C++, der auch für Funktionsparameter gefunden wurde. Überprüfen Sie zum Beispiel operator++(int) - man schreibt nicht operator++(int i) oder so ähnlich.

  3. Was zusammen passiert, ist SFINAE, die eine Abkürzung für Substitution Fehler ist kein Fehler. Es gibt zwei Fälle hier:

    • Erstens, wenn die boolean Argument von std::enable_if_tfalse ist, bekommt man

      template<typename ...Dims, typename = /* not a type */> 
      size_t operator()(Dims ... dims) const; 
      

      Da es keinen gültigen Typ ist auf der rechten Seite von typename = versagt Typ Abzug. Aufgrund von SFINAE führt dies jedoch nicht zu einem Kompilierzeitfehler, sondern zu einem Entfernen der Funktion aus dem Overload-Set.

      Das Ergebnis in der Praxis ist so, als wäre die Funktion nicht definiert worden.

    • Zweitens, wenn das boolean Argument von std::enable_if_ttrue ist, bekommt man

      template<typename ...Dims, typename = void> 
      size_t operator()(Dims... dims) const; 
      

      Jetzt typename = void ist eine gültige Typdefinition und so gibt es keine Notwendigkeit, die Funktion zu entfernen. Es kann somit normalerweise verwendet werden.

zu Ihrem Beispiel angewendet,

template<typename... Dims, 
     typename = Enable_if<All(Convertible<Dims,size_t>()...)>> 
     size_t operator()(Dims... dims) const; 

das bedeutet vor, dass diese Funktion nur, wenn trueAll(Convertible<Dims,size_t>()... ist vorhanden. Das bedeutet im Grunde, dass die Funktionsparameter alle ganzzahlige Indizes sein sollten (ich persönlich würde das in Bezug auf std::is_integral<T> schreiben).

+0

Ein paar Nitpicks (fühlen Sie sich frei, meine Antwort zu nitpick wenn Sie mögen: P), kann die Art Deduktion Scheitern _can_ zu einem Kompilierungsfehler führen Es passiert einfach nicht, wenn stattdessen eine andere Funktionsüberlastung gefunden wird. – SirGuy

+0

Außerdem ist 'Convertible' generischer als' std :: is_integral', da letzteres nach einem Vorkommnis aus einer vordefinierten Liste von Typen sucht, während erstere benutzerdefinierte Typen mit einer Umwandlung in 'std :: size_t akzeptiert '(Ich bin mir ziemlich sicher, dass eine Konvertierung in einen ganzzahligen Typ ausreicht, da danach eine Type-Promotion statt einer Konvertierung durchgeführt wird, um ein' std :: size_t' zu erhalten). – SirGuy

+0

Dies ist ein großer Brocken der Erklärung. Danke vielmals. – Antiusninja

Verwandte Themen