Template Arguments

Modernes C++ Rainer Grimm  –  1 Kommentare

Es ist recht interessant, welche Regeln der Compiler anwendet, um die Template-Argumente abzuleiten. Um es kurz zu machen: Fast immer resultiert der erwartete Datentyp.

Die Regeln gelten nicht nur für Funktions-Templates (C++98), sondern auch für auto (C++11), für Klassen-Templates (C++17) und Concepts (C++20).

Template Arguments

C++ unterstützt die Ableitung von Funktions-Template-Argumenten seit seinen Anfängen. Hier ist eine kurze Rekapitulation.

Funktions-Template Argument Deduktion

Zunächst rufe ich ein Funktions-Template max für int und double auf

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 Template-Argumente aus den Funktionsargumenten ab. C++ Insights zeigt, dass der Compiler ein vollständig spezialisiertes Funktions-Template für max für int (1) und für double (2) erstellt.

Template Arguments

Der Prozess der Template-Typ-Deduktion, wie in diesem Fall, produziert meist den erwarteten Typ. Es ist recht aufschlussreich, diesen Prozess tiefer zu analysieren.

Template Type Deduction

Bei der Ableitung des Template-Typs kommen drei Entitäten ins Spiel: T, ParameterType und der Ausdruck expression.

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

func(expression);

Es werden zwei Typen abgeleitet:

  • T
  • ParameterType

Für ParameterType gibt es die drei Möglichkeiten:

  • Wert
  • Referenz (&) oder Zeiger (*)
  • Univerale Referenz (&&)

Der Ausdruck wiederum kann ein lvalue oder ein rvalue sein. Zusätzlich kann der lvalue oder rvalue eine Referenz, oder const oder volatile qualifiziert sein.

Der einfachste Weg, die Template-Typ-Deduktion zu verstehen, ist, den ParameterType zu variieren.

ParameterType ist ein Wert

Den Parameter als Wert zu nehmen, ist wohl 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 wird erzeugt
  • Wenn newExpr const oder volatile ist, wird const oder volatile ignoriert.

Wenn der ParameterType eine Referenz oder eine universelle Referenz ist, wird die constness (oder volatileness) von expr beachtet.

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

Der Einfachheit halber verwende ich eine Referenz. Die analoge Argumentation gilt für einen Zeiger. Im Wesentlichen ergibt sich das erwartete Ergebnis.

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

func(expr);
  • Wenn expr eine Referenz ist, wird die Referenz ignoriert (aber letztendlich wieder hinzugefügt).
  • Wenn expr mit dem ParameterType übereinstimmt, wird der resultierende Typ zu einer Referenz. Das bedeutet:
    • eine expr vom Typ int wird zu einem int&
    • eine expr vom Typ const int wird zu einem const int&
    • eine expr vom Typ const int& wird zu einem 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, das sein Argument per Wert (1), per Referenz (2) und per universeller Referenz (3) nimmt.

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

  • (1): Beide Aufrufe von funcValue bewirken die gleiche Instanziierung der Funktions-Templates. Der abgeleitete Typ ist ein int.
Template Arguments
  • (2): Der Aufruf der Funktion funcReference mit const int& ergibt den Typ const int&.
Template Arguments
  • (3): Der Aufruf der Funktion funcUniversalReference ergibt eine lvalue-Referenz oder eine rvalue-Referenz.
Template Arguments

Es gibt ein interessantes Verhalten, wenn man die Funktion funcValue mit einem C-Array aufruft. Das C-Array decays (verfällt).

Decay eines C-Arrays

Ein C-Array als Wert anzunehmen ist besonders.

// 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);

}

Wird das Funktions-Template funcValue mit einem C-Array aufgerufen, decayed das C-Array in einen Zeiger auf sein erstes Element. Decay hat viele Facetten. Es wird angewendet, wenn ein Funktionsargument als Wert übergeben wird. Decay bedeutet, dass eine implizite Konvertierung Funktion-zu-Zeiger, Array-zu-Zeiger oder lvalue-zu-rvalue gegebenenfalls angewendet wird. Zusätzlich werden die Referenz eines Typs T und seine const/volatile Qualifizierer entfernt.

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

Template Arguments

Das bedeutet im Wesentlichen, dass die Größe des C-Arrays nicht bekannt ist.

Aber es gibt einen Trick. Wenn man das C-Array per Referenz nimmt und den Typ und die Größe des C-Arrays annimmt, ermittelt der Compiler seine Größe.

// 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';

}

Das Funktions-Template funcArraySize leitet die Größe des C-Arrays ab. Ich habe aus Gründen der Lesbarkeit dem C-Array Parameter den Namen arr gegeben: std::size_t funcArraySize(T (&arr)[N]). Dies ist nicht notwendig und du kannst einfach std::size_t funcArraySize(T (&)[N]) schreiben. Hier sind die Interna aus C++ Insights.

Template Arguments

Zum Abschluss noch die Ausgabe des Programms:

Template Arguments

Dieses Wissen über die automatische Bestimmung der Typen der Template-Argumente, lässt sich direkt auf auto (C++11) anwenden.

auto Typ Deduktion

Die auto Typ-Deduktion verwendet die Regeln der Template-Typ-Deduktion

Zur Erinnerung, dies sind die wesentlichen Entitäten der Template-Typ-Deduktion:

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

auto val = 2011;

Das Verständnis von auto bedeutet, dass auto als Ersatz für T und die Typspezifizierer von auto als Ersatz für den ParameterType in dem Funktions-Template zu betrachten ist.

Der Typspezifizierer 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 vorherige Programm templateTypeDeduction.cpp und verwenden auto anstelle von Funktions-Templates.

// 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();

}

Beim Betrachten der resultierenden Typen in C++ Insights fällt auf, dass sie identisch mit den Typen sind, die im Programm templateTypeDeduction.cpp abgeleitet wurden.

Template Arguments

Natürlich decayed auto auch, wenn es ein C-Array als Wert annimmt.

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

Ich habe das pdf-Bundle vorbereitet. Es zu erhalten ist ganz einfach. Ich verschicke automatisch bei der Anmeldung an meinen deutschen oder englischen Newsletter einen Link zu dem pdf-Bundle.

Hier gibt es mehr Informationen zu dem pdf-Bundle: C++ Coroutines.

Template Arguments

Wie geht's weiter?

C++17 macht Typ-Deduktion mächtiger. 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 Klassen-Template-Argument-Deduktion macht das Programmiererleben viel einfacher.