Die Type-Traits-Bibliothek: Korrektheit

Modernes C++ Rainer Grimm  –  23 Kommentare

Die beiden Hauptziele der Type-Traits-Bibliothek sind sehr überzeugend: Korrektheit und Optimierung. Heute schreibe ich über die Korrektheit.

Die Type-Traits Bibliothek: Korrektheit

Die Type-Traits-Bibliothek ermöglicht es, Typabfragen, Typvergleiche und Typmodifikationen zur Compiletime durchzuführen. In meinem letzten Artikel über die Type-Traits-Bibliothek habe ich nur über Typabfragen und Typvergleiche geschrieben. Bevor ich über den Korrektheitsaspekt der Type-Traits-Bibliothek schreibe, möchte ich kurz auf Typmodifikationen eingehen.

Typmodifikationen

Die Type-Traits-Bibliothek bietet viele Metafunktionen zur Manipulation von Typen. Hier sind die Interessantesten:

// const-volatile modifications:
remove_const;
remove_volatile;
remove_cv;
add_const;
add_volatile;
add_cv;

// reference modifications:
remove_reference;
add_lvalue_reference;
add_rvalue_reference;

// sign modifications:
make_signed;
make_unsigned;

// pointer modifications:
remove_pointer;
add_pointer;

// other transformations:
decay;
enable_if;
conditional;
common_type;
underlying_type;

Um aus einem int oder einem const int einen int zu erhalten, musst du mit ::type nach dem Typ fragen.

std::is_same<int, std::remove_const<int>::type>::value; // true
std::is_same<int, std::remove_const<const int>::type>::value; // true

Seit C++14 kannst du einfach _t verwenden, um den Typ zu erhalten, wie bei std::remove_const_t:

std::is_same<int, std::remove_const_t<int>>::value; // true
std::is_same<int, std::remove_const_t<const int>>::value; // true

Um eine Vorstellung davon zu bekommen, wie nützlich diese Metafunktionen aus der Type-Traits-Bibliothek sind, hier ein paar Beispiele:

  • std::decay: std::thread wendet std::decay auf seine Argumente an. Zu den Argumenten von std::thread gehören die ausgeführte Funktion f und ihre Argumente args. Decay bedeutet, dass implizite Konvertierungen von Array-zu-Zeiger, Funktion-zu-Zeiger durchgeführt werden und const/volatile-Qualifier und -Referenzen entfernt werden.
  • std::enable_if ist eine bequeme Möglichkeit, SFINAE zu verwenden. SFINAE steht für "Substitution Failure Is Not An Error" (Eine fehlgeschlagene Substitution ist kein Fehler) und wird bei der Überladung eines Funktions-Templates angewendet. Das heißt, wenn die Substitution des Template-Parameters fehlschlägt, wird die Spezialisierung aus der Menge aller möglichen Überladungen verworfen, aber dieser Fehler verursacht keinen Compilerfehler.
  • std::conditional ist der ternäre Operator zur Compiletime.
  • std::common_type bestimmt den gemeinsamen Typ aller Typen, in den alle Typen umgewandelt werden können.
  • std::underlying_type bestimmt den Typ einer enum.

Vielleicht bist du noch nicht von den Vorteilen der Type-Traits-Bibliothek überzeugt. Ich möchte meine Beitragsserie daher mit ihren beiden Hauptzielen beenden: Korrektheit und Optimierung.

Korrektheit

Korrektheit bedeutet, dass du die Type-Traits-Bibliothek in C++11 nutzen kannst, um deinen Algorithmus sicherer zu machen. Die folgende Implementierung des gcd-Algorithmus setzt voraus, dass der binäre Modulo-Operator für seine Argumente gültig ist.

// gcd2.cpp

#include <iostream>
#include <type_traits>

template<typename T>
T gcd(T a, T b) {
static_assert(std::is_integral<T>::value, "T should be an integral type!"); // (1)
if( b == 0 ){ return a; }
else{
return gcd(b, a % b);
}
}

int main() {

std::cout << gcd(100, 33) << '\n';
std::cout << gcd(3.5,4.0) << '\n';
std::cout << gcd("100","10") << '\n';

}

Die Fehlermeldung ist eindeutig:

Die Type-Traits Bibliothek: Korrektheit

Der Compiler beschwert sich sofort, dass ein double oder ein const char* kein ganzzahliger Datentyp ist, da der static_assert-Ausdruck in (1) zuschlägt.

Aber Korrektheit bedeutet, dass du die Type-Traits-Bibliotheken nutzen kannst, um Concepts wie Integral, SignedIntegral und UnsignedIntegral in C++20 zu implementieren.

template <typename T>
concept Integral = std::is_integral<T>::value; // (1)

template <typename T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value; // (2)

template <typename T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

Das Concept Integral verwendet direkt die type-traits-Funktionen std::is_integral (1) und das Concept SignedIntegral die type-traits-Funktion std::is_signed (2). Probieren wir es aus und verwenden wir das Concept Integral direkt.

// gcdIntegral.cpp

#include <iostream>
#include <type_traits>

template <typename T>
concept Integral = std::is_integral<T>::value;

template <typename T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value;

template <typename T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

Integral auto gcd(Integral auto a, decltype(a) b) {
if( b == 0 ){ return a; }
else{
return gcd(b, a % b);
}
}

int main() {

std::cout << gcd(100, 33) << '\n';
std::cout << gcd(3.5,4.0) << '\n';
std::cout << gcd("100","10") << '\n';

}

Der gcd-Algorithmus ist nun einfacher zu lesen. Er setzt voraus, dass das erste Argument a und sein Rückgabetyp ganzzahlige Datentypen sind. Um sicherzustellen, dass das zweite Argument b den gleichen Typ hat wie das erste Argument a, habe ich seinen Typ als decltype(a) angegeben. Folglich sind diese Implementierung des gcd-Algorithmus und die vorherige in gcd2.cpp gleichwertig.

Jetzt ist die Fehlermeldung ausführlicher als die vorherige:

Die Type-Traits Bibliothek: Korrektheit

Die Fehlermeldung des GCC ist nicht nur zu langatmig, sondern auch zu schwer zu lesen. Lass mich Clang im Compiler Explorer ausprobieren. Die Fehlermeldung über die fehlerhafte Verwendung von double liest sich wie Prosa:

Die Type-Traits Bibliothek: Korrektheit

Ehrlich gesagt glaube ich nicht, dass eine Fehlermeldung verständlicher formuliert sein könnte.

Schließlich wollte ich noch den neuesten Microsoft Visual Studio Compiler ausprobieren. Dieser Compiler unterstützt Concepts mit einer Ausnahme: der sogenannten Abbreviated Function Template Syntax. Du hast es vielleicht schon erraten. Ich habe die Abbreviated Function Template Syntax in meinem gcd-Algorithmus verwendet. Du kannst mehr über diese schöne Syntax in meinem vorherigen Beitrag lesen: C++20: Concepts - Syntactic Sugar.

Wie geht es weiter?

Du weißt natürlich, worüber ich in meinem nächsten Beitrag schreiben werde: die Performanz-Geschichte der Type-Traits-Bibliothek.