Template-Spezialisierung: Mehr Details zu Klassen-Templates
Nachdem ich in meinem letzten Beitrag die Grundlagen zur Template-Spezialisierung vorgestellt habe, tauche ich heute tiefer ein. Ich möchte die partielle und vollstÀndige Spezialisierung eines Klassen-Templates als Compilezeit if vorstellen.
Nachdem ich in meinem letzten Beitrag [1] die Grundlagen zur Template-Spezialisierung vorgestellt habe, tauche ich heute tiefer ein. Ich möchte die partielle und vollstÀndige Spezialisierung eines Klassen-Templates als Compilezeit if vorstellen.
Spezialisierung von Klassen-Templates als Compilezeit if
Zu meinen EinfĂŒhrungen in die Template-Spezialisierung habe ich ein paar Ă€hnliche Fragen erhalten, darunter: Wie kann man entscheiden, ob ein Datentyp einen gegebenen Typ besitzt oder zwei Typen gleich sind? Die Beantwortung dieser Fragen ist einfacher als es scheinen mag und hilft mir, weitere Theorien ĂŒber die Spezialisierung von Klassen-Templates zu prĂ€sentieren. FĂŒr meine Antwort implementiere ich vereinfachte Versionen von std::is_same [2] und std::remove_reference [3]. Die in diesem Post vorgestellten Techniken sind eine Anwendung der Spezialisierung von Klassen-Templates und stellen ein Compilezeit if dar.
std::is_same
std::is_same ist eine Funktion aus der type-traits-Bibliothek. Sie gibt std::true_type [4] zurĂŒck, wenn beide Typen gleich sind, andernfalls gibt sie std::false_type zurĂŒck. Der Einfachheit halber gebe ich in meiner Implementierung true oder false zurĂŒck.
// isSame.cpp
#include <iostream>
template<typename T, typename U> // (1)
struct isSame {
static constexpr bool value = false;
};
template<typename T> // (2)
struct isSame<T, T> {
static constexpr bool value = true;
};
int main() {
std::cout << '\n';
std::cout << std::boolalpha;
// (3)
std::cout << "isSame<int, int>::value: " << isSame<int, int>::value << '\n';
std::cout << "isSame<int, int&>::value: " << isSame<int, int&>::value << '\n';
int a(2011);
int& b(a); // (4)
std::cout << "isSame<decltype(a), decltype(b)>::value " <<
isSame<decltype(a), decltype(b)>::value << '\n';
std::cout << '\n';
}
Das primĂ€re Template (1) gibt als Default false zurĂŒck, wenn du nach dem Wert fragst. Im Gegensatz dazu gibt die partielle Spezialisierung (2), die verwendet wird, wenn beide Typen gleich sind, true zurĂŒck. Das Klassen-Template isSame lĂ€sst sich auf Datentypen (3) und, dank decltype, auf Werte (4) anwenden. Der folgende Screenshot zeigt die Ausgabe des Programms:
Du ahnst es wohl schon? Das Klassen-Template isSame ist ein Beispiel fĂŒr Template-Metaprogrammierung. Nun muss ich einen kleinen Umweg machen und ein paar Worte ĂŒber Meta verlieren.
Metafunktionen und Metadaten
Zur Laufzeit verwenden wir Daten und Funktionen. Zur Compilezeit verwenden wir Metadaten und Metafunktionen. Ganz einfach, es heiĂt Meta, da wir Metaprogrammierung umsetzen. Was sind Metadaten oder Metafunktionen? Hier ist die erste Definition:
- Metadaten: Typen und Ganzzahlen, die in Metafunktionen verwendet werden.
- Metafunktion: Funktionen, die zur Compilezeit ausgefĂŒhrt werden.
Lass mich die Begriffe Metadaten und Metafunktion nÀher erlÀutern.
Metadaten beinhalten drei EntitÀten:
- Datentypen wie
int,doubleoderstd::string - Nicht-Typen wie Ganzzahlen, Enumeratoren, Zeiger, lvalue-Referenzen und FlieĂkommawerte mit C++20
- Templates
In der Metafunktion isSame habe ich nur Datentypen verwendet.
Datentypen wie das Klassen-Template isSame werden in der Template-Metaprogrammierung verwendet, um Funktionen zu simulieren. Basierend auf meiner Definition von Metafunktionen, können constexpr-Funktionen auch zur Compile Time ausgefĂŒhrt werden und sind somit auch Metafunktionen.
Eine Metafunktion kann nicht nur einen Wert, sie kann auch einen Datentyp zurĂŒckgeben. Per Konvention gibt eine Metafunktion einen Wert mittels ::value und einen Datentyp mittels ::type zurĂŒck. Die folgende Metafunktion removeReference gibt einen Datentyp als Ergebnis zurĂŒck.
// removeReference.cpp
#include <iostream>
#include <utility>
template<typename T, typename U>
struct isSame {
static constexpr bool value = false;
};
template<typename T>
struct isSame<T, T> {
static constexpr bool value = true;
};
template<typename T> // (1)
struct removeReference {
using type = T;
};
template<typename T> // (2)
struct removeReference<T&> {
using type = T;
};
template<typename T> // (3)
struct removeReference<T&&> {
using type = T;
};
int main() {
std::cout << '\n';
std::cout << std::boolalpha;
// (4)
std::cout << "isSame<int, removeReference<int>::type>::value: " <<
isSame<int, removeReference<int>::type>::value << '\n';
std::cout << "isSame<int, removeReference<int&>::type>::value: " <<
isSame<int, removeReference<int&>::type>::value << '\n';
std::cout << "isSame<int, removeReference<int&&>::type>::value: " <<
isSame<int, removeReference<int&&>::type>::value << '\n';
// (5)
int a(2011);
int& b(a);
std::cout << "isSame<int, removeReference<decltype(a)>::type>::value: " <<
isSame<int, removeReference<decltype(a)>::type>::value << '\n';
std::cout << "isSame<int, removeReference<decltype(b)>::type>::value: " <<
isSame<int, removeReference<decltype(b)>::type>::value << '\n';
std::cout << "isSame<int, removeReference<decltype(std::move(a))>::type>::value: " <<
isSame<int, removeReference<decltype(std::move(a))>::type>::value << '\n';
std::cout << '\n';
}
In diesem Beispiel wende ich die zuvor definierte Metafunktion isSame und die Metafunktion removeReference an. Das primĂ€re Template removeReference (1) gibt T mittels des Bezeichners type zurĂŒck. Die partiellen Spezialisierungen fĂŒr die lvalue-Referenz (2) und die rvalue-Referenz geben ebenfalls T zurĂŒck, indem sie die Referenzen von ihrem Template-Parameter entfernen. Wie zuvor lĂ€sst sich die Metafunktion removeReference mit Typen (4) und, dank decltype, mit Werten (5) verwenden. decltype(a) gibt einen Wert, decltype(b) gibt eine lvalue-Referenz und decltype(std::move(a)) gibt eine rvalue-Referenz zurĂŒck.
Zum Abschluss folgt hier die Ausgabe des Programms:
Es gibt eine Falle, in die ich bereits getappt bin. Wenn eine Memberfunktion eines voll spezialisierten Klassen-Templates auĂerhalb der Klasse definiert wird, darf nicht template<> verwendet werden.
Memberfunktionen einer Spezialisierung, die auĂerhalb des Klassenkörpers definiert werden
Das folgende Programm zeigt das Klassen-Template Matrix, das eine partielle und eine vollstÀndige Spezialisierung besitzt.
// specializationExtern.cpp
#include <cstddef>
#include <iostream>
template <typename T, std::size_t Line, std::size_t Column> // (1)
struct Matrix;
template <typename T> // (2)
struct Matrix<T, 3, 3>{
int numberOfElements() const;
};
template <typename T>
int Matrix<T, 3, 3>::numberOfElements() const {
return 3 * 3;
};
template <> // (3)
struct Matrix<int, 4, 4>{
int numberOfElements() const;
};
// template <> // (4)
int Matrix<int, 4, 4>::numberOfElements() const {
return 4 * 4;
};
int main() {
std::cout << '\n';
Matrix<double, 3, 3> mat1; // (5)
std::cout << "mat1.numberOfElements(): " << mat1.numberOfElements() << '\n';
Matrix<int, 4, 4> mat2; // (6)
std::cout << "mat2.numberOfElements(): " << mat2.numberOfElements() << '\n';
std::cout << '\n';
}
(1) deklariert das primĂ€re Template, (2) definiert die partielle Spezialisierung und (3) die vollstĂ€ndige Spezialisierung von Matrix. Die Memberfunktionen numberOfElements werden auĂerhalb des Klassenkörpers definiert. Zeile (4) ist wohl die nicht-intuitive Zeile. Wenn die Memberfunktion numberOfElements auĂerhalb des Klassenkörpers definiert wird, darf kein template <> verwendet werden. Zeile (5) bewirkt die Instanziierung der partiellen und Zeile (6) die Instanziierung der vollstĂ€ndigen Spezialisierung.
Wie geht's weiter?
In meinem nĂ€chsten Beitrag schreibe ich ĂŒber die vollstĂ€ndige Spezialisierung von Funktions-Templates und deren ĂŒberraschendes Zusammenspiel mit Funktionen. Um es kurz zu machen, gemÀà den C++ Core Guidelines gilt: T.144: Don't specialize function templates [5].
( [6])
URL dieses Artikels:
https://www.heise.de/-6121736
Links in diesem Artikel:
[1] https://www.heise.de/blog/Template-Spezialisierung-6118187.html
[2] https://en.cppreference.com/w/cpp/types/is_same
[3] https://en.cppreference.com/w/cpp/types/remove_reference
[4] https://en.cppreference.com/w/cpp/types/integral_constant
[5] https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rt-specialize-function
[6] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien