zurück zum Artikel

Die automatisch Bestimmung der Template Argumente von Klassen-Templates

Rainer Grimm

In meinem letzten Beitrag Template Arguments habe ich über die Typbestimmung von Funktions-Templates (C++98) und auto (C++11) geschrieben. Heute setze ich mir einen moderneren Hut auf. Ich beginne mit der automatischen Typbestimmung von Nicht-Template-Parametern, Klassen-Templates (C++17) und schließe mit der automatischen Typbestimmung von Concepts (C++20) ab.

In meinem letzten Beitrag Template Arguments [1] habe ich über die Typbestimmung von Funktions-Templates (C++98) und auto (C++11) geschrieben. Heute setze ich mir einen moderneren Hut auf. Ich beginne mit der automatischen Typbestimmung von Nicht-Template-Parametern, Klassen-Templates (C++17) und schließe mit der automatischen Typbestimmung von Concepts (C++20) ab.

Die Bestimmung der Template Argumente von Klassen-Templates

Der chronologischen Reihenfolge folgend, möchte ich mit zwei C++17-Features beginnen: Typbestimmung von Nicht-Typ-Template-Parametern und Typbestimmung von Klassen-Templates in C++17.

Zuallererst, was sind Nicht-Typ-Template-Parameter? Das sind nullptr, ganzzahlige Werte wie zum Beispiel bool oder int, lvalue Referenzen, Zeiger, Aufzähler und mit C++20 Fließkommazahlen. Am häufigsten werden ganzzahlige Typen verwendet.

Nach dieser Theorie fahre ich mit einem Beispiel fort:

template <auto N> // (1)
class MyClass{
....
};

template <int N> // (2)
class MyClass<N> {
....
};


MyClass<'x'> myClass1; // (3)
MyClass<2017> myClass2; // (4)

Durch die Verwendung von auto (1) in der Template-Signatur ist N ein Nicht-Typ-Template-Parameter. Der Compiler wird ihn automatisch ableiten.MyClass lässt sich auch für int partiell spezialisieren (2). Die Template-Instanziierung (3) wird das primäre Template (1) verwenden und die folgende Template-Instanziierung die partielle Spezialisierung für int (4).

Die üblichen Typqualifizierer können verwendet werden, um den Typ der Nicht-Typ-Template-Parameter einzuschränken.

template <const auto* p>
class S;

In dieser Deklaration eines Klassen-Template S, muss p ein Zeiger auf const sein.

Die automatische Typbestimmung für Nicht-Typ-Templates lässt sich auch auf variadische Templates anwenden.

template <auto... ns>
class VariadicTemplate{ .... };

template <auto n1, decltype(n1)... ns>
class TypedVariadicTemplate{ .... };

VariadicTemplate kann eine beliebige Anzahl von Nicht-Typ-Template-Parametern ableiten. TypedVariadicTemplate wird nur den ersten Template-Parameter bestimmen. Die restlichen Template-Parameter müssen vom gleichen Typ wie der erste Typ sein: decltype(n1).

Die automatische Typbestimmung von Klassen-Templates macht ihre Verwendung recht komfortabel.

Ein Funktions-Template kann seine Typparameter aus seinen Funktionsargumenten automatisch bestimmen. Aber das war für spezielle Funktionen nicht möglich: Konstruktoren von Klassen-Templates. Mit C++17 ist diese Aussage falsch. Ein Konstruktor kann seine Typparameter von seinen Konstruktorargumenten ableiten. Hier ist ein erstes Beispiel:

// templateArgumentDeduction.cpp

#include <iostream>

template <typename T>
void showMe(const T& t) {
std::cout << t << '\n';
}

template <typename T>
struct ShowMe{
ShowMe(const T& t) {
std::cout << t << '\n';
}
};

int main() {

std::cout << '\n';

showMe(5.5); // not showMe<double>(5.5);
showMe(5); // not showMe<int>(5);

ShowMe(5.5); // not ShowMe<double>(5.5);
ShowMe(5); // not ShowMe<int>(5);

std::cout << '\n';

}

Nun noch ein paar Worte zur main-Funktion. Die Instanziierung des Funktions-Templates showMe ist seit dem ersten C++ Standard C++98 gültig, die Instanziierung der Klassen-Templates ShowMe jedoch erst seit C++17. Aus Benutzersicht fühlt sich die Verwendung von Funktions- oder Klassen-Templates genauso an wie eine gewöhnliche Funktion oder Klasse.

Die automatisch Bestimmung der Template Argumente von Klassen-Templates

Wer noch nicht überzeugt ist, findet hier weitere Beispiele für die Ableitung der Argumente von Klassen-Templates:

// classTemplateArgumentDeduction.cpp

#include <array>
#include <Vektor>
#include <mutex>
#include <memory>

int main() {

std::array myArr{1, 2, 3}; // deduces std::array<int, 3>
std::vector myVec{1.5, 2.5}; // deduces std::vector<double>

std::mutex mut;
std::lock_guard myLock(mut); // deduces std::lock_guard<mutex>(mut)

std::pair myPair(5, 5.5); // deduces std::pair<int, double>
std::tuple myTup(5, myArr, myVec); // deduces std::tuple<int,
// std::array<int, 3>,
// std::vector<double>>
}

Die Kommentare zeigen, wie der C++17 Compiler den Typ bestimmt. Dank C++ Insights [2] lässt sich der Prozess der Bestimmung von Template-Argumenten visualisieren.

Die letzten beiden Beispiele zu std::pair und std::tuple sind ziemlich interessant. Vor C++17 haben wir Fabrikfunktionen wie std::make_pair oder std::make_tuple verwendet, um ein std::pair oder ein std::tuple zu erzeugen, ohne die Typparameter anzugeben. Im Gegensatz zu Klassen-Templates konnte der Compiler die Typparameter automatisch aus den Funktionsargumenten ableiten. Hier ist eine vereinfachte Version von std::make_pair.

// makePair.cpp

#include <utility>

template<typename T1, typename T2>
std::pair<T1, T2> make_pair2(T1 t1, T2 t2) {
return std::pair<T1, T2>(t1, t2);
}

int main() {

auto arg{5.5};
auto pair1 = std::make_pair(5, arg);
auto pair2 = make_pair2(5, arg);
auto pair3 = std::pair(5, arg);

}

Der Compiler bestimmt die gleichen Datentypen für pair1 und pair2. Mit C++17 brauchen wir diese Fabrikfunktion nicht mehr und können direkt den Konstruktor von std::pair aufrufen, um pair3 zu erhalten.

Die automatisch Bestimmung der Template Argumente von Klassen-Templates

Das Programm lässt sich direkt auf C++ Insights [3] studieren.

Es mag verwundern, dass mein Funktions-Template make_pair2 seine Argumente per Wert angenommen hat. std::make_pair decayed seine Argumente und so auch mein Funktions-Template make_pair2. Ich habe über den Decay von Funktionsargumenten in meinem letzten Beitrag Template Arguments [4] geschrieben.

Bevor ich ein paar Worte über die automatische Typbestimmung mit Concepts schreibe, möchte ich es explizit betonen: Die automatische Typbestimmung ist mehr als praktisch und sie ist ein Sicherheitsfeature. Wer den Datentyp nicht angibt, kann dabei keinen Fehler machen.

// automaticTypeDeduction.cpp

#include <string>

template<typename T>
void func(T) {};

template <typename T>
struct Class{
Class(T){}
};

int main() {

int a1 = 5.5; // static_cast<int>(5.5)
auto a2 = 5.5;

func<float>(5.5); // static_cast<float>(5.5)
func(5.5);

Class<std::string> class1("class"); // calls essentially
// std::string("class")
Class class2("class");

}

Alle Fehler sind darauf zurückzuführen, dass ich den Datentyp explizit angegeben habe:

Auf C++ Insights [5] lässt sich das Programm wieder schön studieren.

Der Geschichte der automatischen Typendeduktion ist nicht viel hinzuzufügen, wenn Concepts ins Spiel kommen.

Die automatische Typbestimmung mit Concepts birgt keine Überraschungen.

// typeDeductionConcepts.cpp

#include <concepts>

void foo(auto t) {} // (1)

void bar(std::integral auto t){} // (2)

template <std::regular T> // (3)
struct Class{
Class(T){}
};

int main() {

foo(5.5);
bar(5);
Class cl(true);

}

Unabhängig davon, ob ein uneingeschränkter Platzhalter (auto in Zeile 1), ein eingeschränkter Platzhalter (Concept in Zeile 2) oder ein eingeschränkter Template-Parameter (Concept in Zeile 3) zum Einsatz kommt, bestimmt der Compiler automatisch den erwarteten Datentyp. C++ Insights [6] hilft dabei, die Typbestimmung zu visualisieren.

Die automatisch Bestimmung der Template Argumente von Klassen-Templates


In meinem nächsten Artikel schreibe ich über ein weiteres spannendes Templatefeature: Spezialisierung. Man kann ein Funktions-Template oder Klassen-Template vollständig spezialisieren, und zusätzlich lässt es sich partiell spezialisieren. ( [7])


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

Links in diesem Artikel:
[1] https://heise.de/-6069388
[2] https://cppinsights.io/s/b83f6def
[3] https://cppinsights.io/s/513635bd
[4] https://heise.de/-6069388
[5] https://cppinsights.io/s/c8c70e7c
[6] https://cppinsights.io/s/218e9e55
[7] mailto:rainer@grimm-jaud.de