Alias Templates und Template Parameter

Modernes C++ Rainer Grimm  –  87 Kommentare

Heute schreibe ich über zwei Themen: Alias Templates und Template-Parameter. Alias-Templates sind eine Möglichkeit, einer Familie von Typen einen eingängigen Namen zu geben. Template-Parameter können Typen, Nicht-Typen und Templates selbst sein.

Alias Templates und Template Parameter

Mit den Alias Templates möchte ich beginnen.

Alias-Templates

Seit C++11 unterstützt die Programmiersprache Alias Templates. Sie bieten eine Möglichkeit, einer Familie von Typen einen lesbaren Namen zu geben. Der folgende Codeschnipsel zeigt die Idee für das Klassen-Template Matrix.

template <typename T, int Line, int Col>
class Matrix{
....
};

Matrix besitzt drei Template-Parameter. Den Typ-Parameter T und die Nicht-Typ-Parameter Line und Col (Details zu den Template-Parametern folgen im nächsten Abschnitt).

Aus Gründen der Lesbarkeit möchte ich zwei spezielle Matrizen haben: Square und Vector. Die Anzahl der Zeilen und Spalten der Matrix Square sollten gleich sein. Die Zeilenzahl von Vector sollte eins sein. Dank Typ-Aliasen kann ich meine Ideen direkt im Code ausdrücken.

template <typename T, int Line>
using Square = Matrix<T, Line, Line>; // (1)

template <typename T, int Line>
using Vector = Matrix<T, Line, 1>; // (2)

Das Schlüsselwort using ((1) und (2)) deklariert einen Typ-Alias. Während das primäre Template Matrix in den drei Dimensionen T, Line und Col parametrisiert werden kann, reduzieren die Typ-Aliase Square und Vector die Parametrisierung auf die zwei Dimensionen T und Line. Aus dieser Sicht ermöglichen es Alias Templates, intuitive Namen für teilweise gebundene Templates zu erstellen. Die Verwendung von Square und Vector ist intuitiv.

Matrix<int, 5, 3> ma;
Square<double, 4> sq;
Vector<char, 5> vec;

Ein anschaulicher Anwendungsfall von Alias Templates ist die type-traits Bibliothek.

Type-Traits Bibliothek

Wenn du std::move(arg) auf einen Wert arg anwendest, greift der Compiler typischerweise auf std::remove_reference zurück, um eine Referenz vom zugrunde liegenden Datentyp zu entfernen:

static_cast<std::remove_reference<decltype(arg)>::type&&>(arg);   // (1)

static_cast<std::remove_reference_t<decltype(arg)>&&>(arg); // (2)

Dank der Alias Templates ist die Version (Zeile 2) seit C++14 gültig. Der folgende Hilfs-Datentyp ist verfügbar:

template< class T >
using remove_reference_t = typename remove_reference<T>::type;

Natürlich bietet die type-traits Bibliothek die entsprechenden Hilfs-Datentypen für weitere Funktionen, die ein Datentyp anbietet.

Das zuvor definierte Klassen-Template Matrix verwendet die beiden Nicht-Typ-Template-Parameter Line und Col.

Template-Parameter

Template-Parameter können Typen, Nicht-Typen und Templates selbst sein.

Typen

Typen sind die am häufigsten verwendeten Template-Parameter. Hier ein paar Beispiele:

std::vector<int> myVec;
std::map<std::string, int> myMap;
std::lock_guard<std::mutex> myLockGuard;
Nicht-Typen

Nicht-Typen können sein:

  • lvalue-Referenz
  • nullptr
  • Zeiger
  • Aufzähler einer enum
  • Ganzzahl-Werte
  • Fließkomma-Werte (C++20)

Ganzzahl-Werte sind die am häufigsten verwendeten Nicht-Typen. std::array ist das typische Beispiel, weil seine Größe zur Compilezeit spezifiziert werden muss:

std::array<int, 3> myArray{1, 2, 3};
Templates

Templates selbst können Template-Parameter sein. Ihre Definition wirkt auf den ersten Blick ein wenig eigentümlich:

// templateTemplateParameters.cpp

#include <iostream>
#include <list>
#include <vector>
#include <string>

template <typename T, template <typename, typename> class Cont > // (1)
class Matrix{
public:
explicit Matrix(std::initializer_list<T> inList): data(inList) { // (2)
for (auto d: data) std::cout << d << " ";
}
int getSize() const{
return data.size();
}

private:
Cont<T, std::allocator<T>> data; // (3)

};

int main(){

std::cout << '\n';

// (4)
Matrix<int, std::vector> myIntVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::cout << '\n';
std::cout << "myIntVec.getSize(): " << myIntVec.getSize() << '\n';

std::cout << std::endl;

Matrix<double, std::vector> myDoubleVec{1.1, 2.2, 3.3, 4.4, 5.5}; // (5)
std::cout << '\n';
std::cout << "myDoubleVec.getSize(): " << myDoubleVec.getSize() << '\n';

std::cout << '\n';
// (6)
Matrix<std::string, std::list> myStringList{"one", "two", "three", "four"};
std::cout << '\n';
std::cout << "myStringList.getSize(): " << myStringList.getSize() << '\n';

std::cout << '\n';

}

Matrix ist ein Klassen-Template, das mit einer std::initializer_list (Zeile 2) initialisiert werden muss. Eine Matrix kann mit einem std::vector (Zeile 4 und Zeile 5), oder einer std::list (Zeile 6) verwendet werden, um ihre Werte zu halten. So weit, nichts Besonderes.

Alias Templates und Template Parameter


Aber halt, ich habe vergessen, Zeile 1 und Zeile 3 zu erläutern. Zeile 1 deklariert ein Klassen-Template, das zwei Template-Parameter besitzt. Okay, der erste Parameter ist der Typ der Elemente und der zweite Parameter steht für den Container. Schauen wir uns den zweiten Parameter genauer an: template <typename, typename> class Cont >. Das bedeutet, dass der zweite Template-Parameter ein Template sein soll, das zwei Template-Parameter benötigt. Der erste Template-Parameter ist der Datentyp der Elemente, die der Container speichert, und der zweite Template-Parameter ist der Allokator, den jeder Container der Standard-Template-Bibliothek besitzt. Auch der Allokator hat einen Defaultwert, wie im Fall eines std::vector. Der Allokator hängt vom Datentyp der Elemente ab.

template<
class T,
class Allocator = std::allocator<T>
> class vector;

Zeile 3 zeigt die Verwendung des Allokators im intern verwendeten Container. Die Matrix kann alle Container verwenden, die von der Art container<Typ der Elemente, Allocator der Elemente> sind. Dies gilt für die Sequenzcontainer wie std::vector, std::deque oder std::list. std::array und std::forward_list würden scheitern, da std::array einen zusätzlichen Nicht-Typ zur Angabe seiner Größe zur Compilezeit benötigt und std::forward_list die size-Memberfunktion nicht unterstützt.

Wen das Schlüsselwort class für den Namen des Template-Parameters irritiert – mit C++17 kann dafür typename verwendet werden:

template <typename T, template <typename, typename> class Cont >    // (1)
class Matrix;

template <typename T, template <typename, typename> typename Cont > // (2)
class Matrix;

Zeile (2) ist seit C++17 gültig und äquivalent zu Zeile (1).

Das nächste pdf-Bundle: Coroutines

In dem Artikel "Which pdf bundle do you want? Make your choice!" fiel die Wahl auf das Coroutine-Bundle.

Alias Templates und Template Parameter

Ich bin noch dabei, das Bundle vorzubereiten. In den nächsten Tagen sollte es aber verfügbar sein.

Wenn du den englischen oder deutschen Newsletter abonnierst, bekommst du automatisch den Link zum aktuellen pdf-Bundle. Schau mal rechts oben auf dieser Seite nach. Dieser Automatismus macht es für mich ganz bequem. Leute, die bereits meinen Newsletter abonniert haben, bekommen den Link automatisch.

Wie geht's weiter?

In meinem nächsten Artikel schreibe ich über Template-Argumente. Es ist sehr interessant, wie der Compiler automatisch die Datentypen bestimmt. 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).