Die Type-Traits-Bibliothek: Korrektheit

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

vorlesen Druckansicht 23 Kommentare lesen
Lesezeit: 6 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

Die beiden Hauptziele der Type-Traits-Bibliothek sind sehr ĂĽberzeugend: Korrektheit und Optimierung. Heute schreibe ich ĂĽber die 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.

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 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:

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 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:

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.

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