zurück zum Artikel

Variadic Templates oder die Power der drei Punkte

Rainer Grimm

Ein Variadic Template kann eine beliebige Anzahl von Template-Parametern besitzen. Dieses Feature mag auf den ersten Blick magisch erscheinen. Daher ist es Zeit, Variadic Templates zu entmystifizieren.

Ein Variadic Template kann eine beliebige Anzahl von Template-Parametern besitzen. Dieses Feature mag auf den ersten Blick magisch erscheinen. Daher ist es Zeit, Variadic Templates zu entmystifizieren.

Variadic Templates oder die Power der drei Punkte

Manch einer mag sich wundern, dass meine Grafik mit den vorgestellten Themen die Template-Instanziierung enthält. Der Grund ist einfach: Nach meinem letzten Artikel über "Template-Instanziierung [1]" hat einer meiner deutschsprachigen Leser (Pseudonym Urfahraner Auge) in einem Kommentar geschrieben, dass es einen wichtigen Unterschied zwischen impliziter und expliziter Instanziierung eines Templates gebe, den ich vergessen habe vorzustellen. Er hat recht. Die implizite Instanziierung von Templates ist lazy (faul), aber die explizite Instanziierung von Templates ist eager (gierig).

Die Template-Instanziierung ist lazy. Das heißt, dass eine nicht benötigte Memberfunktion eines Klassen-Templates nicht instanziiert wird. Nur die Deklaration der Member-Funktion ist verfügbar, aber nicht ihre Definition. Damit ist es möglich, ungültigen Code in einer Menber-Funktion zu verwenden, solange sie nicht aufgerufen wird.

// numberImplicitExplicit.cpp

#include <cmath>
#include <string>

template <typename T>
struct Number {
int absValue() {
return std::abs(val);
}
T val{};
};

// template class Number<std::string>; // (2)
// template int Number<std::string>::absValue(); // (3)

int main() {

Number<std::string> numb;
// numb.absValue(); // (1)

}

Wer die Member-Funktion numb.absValue() (1) aufruft, bekommst erwartungsgemäß eine Fehlermeldung zur Kompilierzeit, die besagt, dass es keine Überladung std::abs für std::string gibt. Hier sind die ersten beiden Zeilen der ausführlichen Fehlermeldung:

Variadic Templates oder die Power der drei Punkte


Nun möchte ich die Template-Instanziierung genauer erklären: Die implizite Instanziierung von Templates ist lazy, aber die explizite Instanziierung von Templates ist eager.

Wer (2) aktiviert (template class number<std::string>) und damit explizit das Klassen-Template Number instanziiert oder (3) aktiviert (template int Number<std::string>::absValue()) und damit explizit die Member-Funktion absValue für std::string instanziiert, bekommt einen Kompilierzeitfehler. Dieser ist äquivalent zum Compiler-Fehler beim Aufruf der Member-Funktion absValue in (1) (numb.absValue()). Hier sind noch einmal die ersten beiden Zeilen der Fehlermeldungen nach dem Aktivieren von (2) oder (3).

Variadic Templates oder die Power der drei Punkte
Variadic Templates oder die Power der drei Punkte

Ich freue mich sehr über Kommentare zu meinen Beiträgen. Sie helfen mir, über die Inhalte zu schreiben, die die Leser meines Blogs interessieren. Vor allem die deutsche Community ist sehr engagiert.

Nun aber endlich zu etwas ganz anderem: Variadic Templates.

Ein Variadic Template ist ein Template, das eine beliebige Anzahl von Template-Parametern besitzen kann. Dieses Feature mag beim ersten Mal magisch erscheinen.

template <typename ... Args>
void variadicTemplate(Args ... args) {
. . . . // four dots
}

Durch die Ellipse (...) werden Args beziehungsweise args zu einem sogenannten Parameterpack. Genauer gesagt ist Args ein Template-Parameterpack und args ein Funktionsparameterpack. Mit Ersterem sind zwei Operationen möglich: Es kann gepackt und entpackt werden. Wenn die Ellipse links von Args steht, wird das Parameterpack gepackt, wenn sie rechts steht, wird es entpackt. Durch Function Template Argument Deduction kann der Compiler die Template Argumente automatisch ableiten.

Variadic Templates werden oft in der Standard Template Library und auch in der Kernsprache verwendet.

template <typename... Types>                                        // (1)
class tuple;

template <typename Callabe, typename... Args > // (2)
explicit thread(Callable&& f, Args&&... args);

template <typename Lockable1, typename Lockable2,
typename... LockableN> // (3)
void lock( Lockable1& lock1, Lockable2& lock2, LockableN&... lockn );

sizeof...(ParameterPack); // (4)

Alle vier Beispiele aus dem C++11 Standard verwenden Variadic Templates. Die ersten drei sind Teil der Standard Template Library. Was lässt sich aus den Deklarationen bestimmen?

Der sizeof...-Operator ist besonders, da hier Parameterpacks auf der C++ Kernsprache zum Einsatz kommen.

Mithilfe des sizeof...-Operators kann direkt ermittelt werden, wie viele Elemente ein Parameterpack enthält. Die Elemente werden dabei nicht ausgewertet.

// printSize.cpp

#include <iostream>

using namespace std::literals;

template <typename ... Args>
void printSize(Args&& ... args){
std::cout << sizeof...(Args) << ' '; // (1)
std::cout << sizeof...(args) << '\n'; // (2)
}

int main() {

std::cout << '\n';

printSize(); // (3)
printSize("C string", "C++ string"s, 2011, true); // (4)

std::cout << '\n';

}

Der sizeof...-Operator erlaubt es, die Größe des Template-Parameterpacks (1) und des Funktionsparameterpacks (2) zur Kompilierzeit zu bestimmen. Ich wende ihn auf ein leeres Parameterpaket (3) und ein Parameterpaket mit vier Elementen an. Das erste Element ist ein C-String und das zweite ein C++-String. Um das C++-String-Literal zu verwenden, muss ich den Namensraum std::literals (5) einbinden. C++14 unterstützt C++-String-Literale.

Variadic Templates oder die Power der drei Punkte

In meinem nächsten Beitrag tauche ich tiefer in Variadic Templates ein und stelle das funktionale Muster zur Auswertung eines Variadic Template vor. Außerdem präsentiere ich die perfekte Fabrikfunktion und springe von C++11 sechs Jahre weiter: Fold Expression in C++17. ( [3])


URL dieses Artikels:
https://www.heise.de/-6157802

Links in diesem Artikel:
[1] https://heise.de/-6151298
[2] https://www.heise.de/developer/artikel/Template-Arguments-6069388.html
[3] mailto:rainer@grimm-jaud.de