Die automatisch Bestimmung der Template Argumente von Klassen-Templates
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.
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.
Automatische Typbestimmung von Nicht-Typ-Template-Parametern
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.
Automatische Typbestimmung von Klassen-Templates
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.
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.
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:
-
int a1
löst die narrowing conversion vondouble
nachint
aus func<float>(5.5)
bewirkt die Umwandlung von demdouble
-Wert 5.5 infloat
Class<std::string> class1("class")
erzeugt einen C++-String, der mit einem C-String initialisiert wird.
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.
Automatische Typbestimmung mit Concepts
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.
Wie geht's weiter?
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
Copyright © 2021 Heise Medien