Vorlagenargumente

Vorlagenargumente

Es ist ziemlich interessant, wie der Compiler die Typen für die Template-Argumente herleitet. Um es kurz zu machen, Sie bekommen die meiste Zeit den Typ, den Sie erwarten. Die Regeln gelten nicht nur für Funktionsvorlagen (C++98), sondern auch für auto (C++11), Klassenvorlagen (C++17) und Konzepte (C++20).

C++ unterstützt von Anfang an die Argumentableitung von Funktionsvorlagen. Hier ist eine kurze Zusammenfassung.

Ableitung von Funktionsvorlagenargumenten

Lassen Sie mich eine Funktionsvorlage max aufrufen für int und doppelt

template <typename T>
T max(T lhs, T rhs) {
 return (lhs > rhs)? lhs : rhs;
}

int main() {
 
 max(10, 5); // (1)
 max(10.5, 5.5); // (2)
 
}

In diesem Fall leitet der Compiler die Vorlagenargumente von den Funktionsargumenten ab. C++ Insights zeigt, dass der Compiler eine vollständig spezialisierte Funktionsvorlage für max erstellt für int (1) und für double (2).

Der Prozess der Schablonentypableitung wie in diesem Fall erzeugt meistens den erwarteten Typ. Es ist sehr aufschlussreich, diesen Prozess genauer zu analysieren.

Vorlagentypableitung

Beim Ableiten des Vorlagentyps kommen drei Entitäten ins Spiel:T, ParameterType und expression.

template <typename T>
void func(ParameterType param);

func(expression);

Zwei Typen werden abgeleitet:

  • T
  • ParameterType

Die ParameterType kann ein

sein
  • Wert
  • Referenz (&) oder Zeiger (*)
  • Universelle Referenz (&&)

Der expression kann einen lvalue oder einen rvalue haben. Außerdem kann der lvalue oder rvalue eine Referenz oder const sein /volatile qualifiziert.

Der einfachste Weg, den Prozess der Vorlagentypableitung zu verstehen, besteht darin, den ParameterType zu variieren .

ParameterType ist ein Wert

Die Annahme des Parameters nach Wert ist wahrscheinlich die am häufigsten verwendete Variante.

template <typename T>
void func(T param);

func(expr);

  • Wenn expr eine Referenz ist, wird die Referenz ignoriert => newExpr erstellt
  • Bei newExpr ist const oder volatile , const oder volatile wird ignoriert.

Wenn der ParameterType eine Referenz oder eine universelle Referenz ist, die Konstanz (oder Flüchtigkeit) von expr respektiert wird.

ParameterType ist eine Referenz (&) oder ein Zeiger (*)

Der Einfachheit halber verwende ich eine Referenz. Die analoge Argumentation gilt für einen Zeiger. Im Wesentlichen erhalten Sie genau das Ergebnis, das Sie erwarten.

template <typename T>
void func(T& param);
// void func(T* param);

func(expr);

  • Bei expr eine Referenz ist, wird die Referenz ignoriert (aber am Ende hinzugefügt).
  • Der expr stimmt mit ParameterType überein und der resultierende Typ wird zu einer Referenz. Das bedeutet,
    • ein expr vom Typ int wird zu einem int&
    • ein expr vom Typ const int wird zu const int&
    • ein expr vom Typ const int& wird zu const int&

ParameterType ist eine universelle Referenz (&&)

template <typename T>
void func(T&& param);

func(expr);

  • Wenn expr ein Lvalue ist, wird der resultierende Typ zu einer Lvalue-Referenz.
  • Wenn expr ein rvalue ist, wird der resultierende Typ zu einer rvalue-Referenz.

Zugegeben, diese Erklärung war ziemlich technisch. Hier ist ein Beispiel.

// templateTypeDeduction.cpp

template <typename T>
void funcValue(T param) { }

template <typename T>
void funcReference(T& param) { }

template <typename T>
void funcUniversalReference(T&& param) { }

class RVal{};

int main() {

 const int lVal{};
 const int& ref = lVal;
 
 funcValue(lVal); // (1)
 funcValue(ref);
 
 funcReference(lVal); // (2)
 
 funcUniversalReference(lVal); // (3)
 funcUniversalReference(RVal());

}

Ich definiere und verwende ein Funktions-Template, dessen Argument der Wert (1), die Referenz (2) und die universelle Referenz (3) ist.

Dank C++ Insights kann ich die Typableitung des Compilers visualisieren.

  • (1) :Beide Aufrufe von funcValue bewirken dieselbe Instanziierung der Funktionsvorlage. Der abgeleitete Typ ist ein int .

  • (2) :Aufruf der Funktion funcReference mit const int& ergibt den Typ const int& .

  • (3) :Verwendung der Funktion funcUniversalReference Geben Sie eine lvalue-Referenz oder eine rvalue-Referenz an.

Es gibt eine interessante Tatsache, wenn Sie die Funktion funcValue aufrufen mit einem C-Array. Das C-Array zerfällt.

Zerfall eines C-Arrays

Das Nehmen eines C-Arrays nach Wert ist etwas Besonderes.

// typeDeductionArray.cpp

template <typename T>
void funcValue(T param) { }

int main() {

 int intArray[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 funcValue(intArray);
 
}

Wenn Sie die Funktionsvorlage funcValue aufrufen bei einem C-Array zerfällt das C-Array zu einem Zeiger auf sein erstes Element. Verfall hat viele Facetten. Es wird angewendet, wenn ein Funktionsargument als Wert übergeben wird. Decay bedeutet, dass eine implizite Konvertierungsfunktion-zu-Zeiger, Array-zu-Zeiger oder lvalue-zu-rvalue angewendet wird. Außerdem werden die Referenz eines Typs T und seine const-volatile-Qualifizierer entfernt.

Hier ist der Screenshot des Programms von C++ Insights.

Dies bedeutet im Wesentlichen, dass Sie die Größe des C-Arrays nicht kennen.

Aber es gibt einen Trick. Wenn Sie das C-Array durch Referenz und Musterabgleich des Typs und der Größe auf dem C-Array nehmen, erhalten Sie die Größe des C-Arrays:

// typeDeductionArraySize.cpp

#include <cstddef>
#include <iostream>

template <typename T, std::size_t N>
std::size_t funcArraySize(T (&arr)[N]) { 
 return N;
}

int main() {

 std::cout << '\n';

 int intArray[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 funcArraySize(intArray);

 std::cout << "funcArraySize(intArray): " << funcArraySize(intArray) << '\n';

 std::cout << '\n';
 
}

Die Funktionsvorlage funcArraySize leitet die Größe der C-Arrays ab. Aus Gründen der Lesbarkeit habe ich dem C-Array-Parameter den Namen arr: std::size_t funcArraySize(T (&arr)[N]) gegeben . Dies ist nicht notwendig und Sie können einfach std::size_t funcArraySize(T (&)[N]) schreiben . Hier sind die Interna von C++ Insights.

Abschließend die Ausgabe des Programms:

Wenn Sie die Ableitung von Vorlagentypen verstehen, verstehen Sie im Wesentlichen auto Typableitung in C++11.

auto Typabzug

auto Die Typableitung verwendet die Regeln der Template-Typableitung.

Zur Erinnerung, dies sind die wesentlichen Entitäten der Vorlagentypableitung:

template <typename T> 
void func(ParameterType param);

auto val = 2011;

auto verstehen bedeutet, dass Sie auto beachten müssen als Ersatz für T und die Typbezeichner von auto als Ersatz für ParameterType in der Funktionsvorlage.

Der Typbezeichner kann ein Wert (1), eine Referenz (2) oder eine universelle Referenz (3) sein.

auto val = arg; // (1)

auto& val = arg; // (2)

auto&& val = arg; // (3)

Probieren wir es aus und ändern das bisherige Programm templateTypeDeduction.cpp und verwenden Sie auto statt Funktionsvorlagen.

// autoTypeDeduction.cpp

class RVal{};

int main() {

 const int lVal{};
 const int& ref = lVal;
 
 auto val1 = lVal; // (1)
 auto val2 = ref;
 
 auto& val3 = lVal; // (2)
 
 auto&& val4 = lVal; // (3)
 auto&& val5 = RVal();

}

Wenn Sie die resultierenden Typen in C++ Insights untersuchen, sehen Sie, dass sie mit den im Programm templateTypeDeduction.cpp abgeleiteten Typen identisch sind .

Natürlich auto zerfällt auch, wenn es ein C-Array nach Wert nimmt.

Das neue pdf-Bundle ist fertig:C++20 Coroutines

Ich habe das pdf-Paket vorbereitet. Es zu bekommen ist ganz einfach. Wenn Sie meinen deutsch- oder englischsprachigen Newsletter abonnieren, erhalten Sie den Link zum pdf-Bundle. Hier finden Sie weitere Informationen zum pdf-Bundle:C++ Coroutines.

Was kommt als nächstes?

C++17 macht die Typableitung leistungsfähiger. Erstens ist eine automatische Typableitung für Nicht-Typ-Template-Parameter möglich, und zweitens können Klassen-Templates auch ihre Argumente ableiten. Insbesondere die Ableitung von Klassenvorlagenargumenten macht das Leben eines Programmierers viel einfacher.