Template Arguments
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.
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).
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.
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
odervolatile
ist, wirdconst
odervolatile
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 demParameterType
ĂŒbereinstimmt, wird der resultierende Typ zu einer Referenz. Das bedeutet: - eine
expr
vom Typint
wird zu einemint&
- eine
expr
vom Typconst int
wird zu einemconst int&
- eine
expr
vom Typconst int&
wird zu einemconst int&
ParameterType ist eine Universelle Referenz (&&)
template <typename T>
void func(T&& param);
func(expr);
- Wenn
expr
einlvalue
ist, wird der resultierende Typ zu einer lvalue-Referenz. - Wenn
expr
einrvalue
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 [1] kann ich die Typableitung des Compilers visualisieren.
- (1): Beide Aufrufe von
funcValue
bewirken die gleiche Instanziierung der Funktions-Templates. Der abgeleitete Typ ist einint
.
- (2): Der Aufruf der Funktion
funcReference
mitconst int&
ergibt den Typconst int&
.
- (3): Der Aufruf der Funktion
funcUniversalReference
ergibt eine lvalue-Referenz oder eine rvalue-Referenz.
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 [2].
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 [3].
Zum Abschluss noch die Ausgabe des Programms:
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 [4] fÀllt auf, dass sie identisch mit den Typen sind, die im Programm templateTypeDeduction.cpp
abgeleitet wurden.
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 [5].
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. ( [6])
URL dieses Artikels:
https://www.heise.de/-6069388
Links in diesem Artikel:
[1] https://cppinsights.io/s/6bb71783
[2] https://cppinsights.io/s/910a53e4
[3] https://cppinsights.io/s/6e908572
[4] https://cppinsights.io/s/2c652b47
[5] https://www.modernescpp.com/index.php/the-new-pdf-bundle-is-ready-coroutines
[6] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien