Die Type-Traits Bibliothek: Typprüfungen
Die Type-Traits Bibliothek ist Bestandteil von C++11 und unterstützt Typprüfungen, Typvergleiche und Typänderungen zur Compiletime. Die Bibliothek umfasst mehr als 100 Funktionen und wächst mit jeder neuen C++-Standardversion.
Die Type-Traits Bibliothek ist Bestandteil von C++11 und unterstützt Typprüfungen, Typvergleiche und Typänderungen zur Compiletime. Die Bibliothek umfasst mehr als 100 Funktionen und wächst mit jeder neuen C++-Standardversion.
In diesem Artikel stelle ich Typprüfungen mir der Type-Traits Bibliothek genauer vor.
![Die Type-Traits Bibliothek: Typprüfungen](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/2/1/8/1/1/3/templatesTypeTraits-2d5d787fcdcf5d0f.png)
Typprüfung
Jeder Typ gehört genau zu einer der vierzehn primären Typkategorien.
Primäre Typkategorien
Hier sind alle Kategorien:
template <class T> struct is_void;
template <class T> struct is_integral;
template <class T> struct is_floating_point;
template <class T> struct is_array;
template <class T> struct is_pointer;
template <class T> struct is_null_pointer;
template <class T> struct is_member_object_pointer;
template <class T> struct is_member_function_pointer;
template <class T> struct is_enum;
template <class T> struct is_union;
template <class T> struct is_class;
template <class T> struct is_function;
template <class T> struct is_lvalue_reference;
template <class T> struct is_rvalue_reference;
Das folgende Programm stellt für jede primäre Typkategorie einen Datentyp vor:
// primaryTypeCategories.cpp
#include <iostream>
#include <type_traits>
struct A {
int a;
int f(int) { return 2011; }
};
enum E {
e= 1,
};
union U {
int u;
};
int main() {
using namespace std;
cout << boolalpha << '\n';
cout << is_void<void>::value << '\n'; // true
cout << is_integral<short>::value << '\n'; // true
cout << is_floating_point<double>::value << '\n'; // true
cout << is_array<int []>::value << '\n'; // true
cout << is_pointer<int*>::value << '\n'; // true
cout << is_null_pointer<nullptr_t>::value << '\n'; // true
cout << is_member_object_pointer<int A::*>::value << '\n'; // true
cout << is_member_function_pointer<int (A::*)(int)>::value <<'\n';// true
cout << is_enum<E>::value << '\n'; // true
cout << is_union<U>::value << '\n'; // true
cout << is_class<string>::value << '\n'; // true
cout << is_function<int * (double)>::value << '\n'; // true
cout << is_lvalue_reference<int&>::value << '\n'; // true
cout << is_rvalue_reference<int&&>::value << '\n'; // true
}
Wie funktioniert dieser Zauber?
Diese Technik basiert auf Templates und Template-Spezialisierung, ein paar Konventionen und einer Menge Tipparbeit. Ich habe eine vereinfachte Version des Funktions-Templates std::integra
l implementiert. std::integra
l prüft, ob ein gegebener Typ ein integral Typ ist. Ich ignoriere dabei const
oder volatile
Qualifier.
// integral.cpp
#include <iostream>
#include <type_traits>
namespace rgr{
template<class T, T v>
struct integral_constant {
static constexpr T value = v;
typedef T value_type;
typedef integral_constant type;
constexpr operator value_type() const noexcept { return value; }
//since c++14:
constexpr value_type operator()() const noexcept { return value; }
};
typedef integral_constant<bool, true> true_type; // (2)
typedef integral_constant<bool, false> false_type;
template <class T>
struct is_integral : public false_type{};
template <>
struct is_integral<bool> : public true_type{};
template <>
struct is_integral<char> : public true_type{};
template <>
struct is_integral<signed char> : public true_type{};
template <>
struct is_integral<unsigned char> : public true_type{};
template <>
struct is_integral<wchar_t> : public true_type{};
template <>
struct is_integral<short> : public true_type{};
template <>
struct is_integral<int> : public true_type{}; // (3)
template <>
struct is_integral<long> : public true_type{};
template <>
struct is_integral<long long> : public true_type{};
template <>
struct is_integral<unsigned short> : public true_type{};
template <>
struct is_integral<unsigned int> : public true_type{};
template <>
struct is_integral<unsigned long> : public true_type{};
template <>
struct is_integral<unsigned long long> : public true_type{};
}
int main(){
std::cout << std::boolalpha << '\n';
std::cout << "std::is_integral<int>::value: "
<< std::is_integral<int>::value << '\n';
std::cout << "rgr::is_integral<int>::value: "
<< rgr::is_integral<int>::value << '\n'; // (1)
std::cout << "std::is_integral<double>::value: "
<< std::is_integral<double>::value << '\n';
std::cout << "rgr::is_integral<double>::value: "
<< rgr::is_integral<double>::value << '\n';
std::cout << '\n';
std::cout << "std::true_type::value: " << std::true_type::value << '\n';
std::cout << "rgr::true_type::value: " << rgr::true_type::value << '\n';
std::cout << "std::false_type::value: " << std::false_type::value << '\n';
std::cout << "rgr::false_type::value: " << rgr::false_type::value << '\n';
std::cout << '\n';
std::cout << "std::integral_constant<bool, true>::value: "
<< std::integral_constant<bool, true>::value << '\n';
std::cout << "rgr::integral_constant<bool, true>::value: "
<< rgr::integral_constant<bool, true>::value << '\n';
std::cout << "std::integral_constant<bool, false>::value: "
<< std::integral_constant<bool, false>::value << '\n';
std::cout << "rgr::integral_constant<bool, false>::value: "
<< rgr::integral_constant<bool, false>::value << '\n';
std::cout << '\n';
}
Ich verwende in meiner Implementierung den Namespace rgr
und vergleiche die Funktionen mit den entsprechenden Type-Traits-Funktionen im Namespace std
. Der Aufruf der Funktionsvorlage rgr::is_integral<int>::value
(1) bewirkt den Aufruf des Ausdrucks rgr::true_type::value
(2), weil integral<int>
von true_type
(3) abgeleitet ist. rgr::true_type::value
ist ein Alias für rgr::integral_constant<bool, true>::value
(2). Im Beispiel verwende ich den statischen constexpr value
der Klasse integral_constant
. integral_constant
ist die Basisklasse der Type-Traits-Funktionen.
Der Vollständigkeit halber ist hier die Ausgabe des Programms. Meine Implementierung liefert die gleichen Ergebnisse, wie die Funktionen aus der Type-Traits Bibliothek.
![Die Type-Traits Bibliothek: Typprüfungen](https://heise.cloudimg.io/width/632/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/2/1/8/1/1/3/integral-b369507dc4d90496.png)
Ich habe in meinem Funktions-Template rgr::is_integral ::value
als Rückgabe verwendet. Hier möchte ich an diese Konvention aus meinem vorherigen Beitrag "Template Metaprogrammierung: Wie es funktioniert [1]" erinnern: Meine Funktions-Templates rgr::is_integra
l sind Metafunktionen und sie verwenden die Namenskonventionen der Template-Metaprogrammierung. Seit C++17 gibt es für Konventionen eine Hilfsklasse für ::value
. Diese Hilfsklasse basiert auf Variablen-Templates.
template< Klasse T >
inline constexpr bool is_integral_v = is_integral<T>::value
Variablen-Templates stellen im Wesentlichen eine Familie von Variablen dar. Dank dieser Hilfsklasse kann man std::integral_v<T>
anstelle von std::integral<T>::value
verwenden. Diese verkürzte Schreibweise funktioniert für alle Funktions-Templates der Type-Traits Library.
Zusammengesetzte Typkategorien werden aus den primären Typkategorien zusammengesetzt.
Zusammengesetzte Typkategorien
Es gibt sieben zusammengesetzte Typkategorien. Die folgende Tabelle zeigt sie.
![Die Type-Traits Bibliothek: Typprüfungen](https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/2/1/8/1/1/3/CompositeTypeCategories-dfed6b243638ac7d.png)
Zusätzlich zu den primären Typkategorien und den zusammengesetzten Typkategorien bietet die Type-Traits Bibliothek Typeneigenschaften und die Abfrage von Typeneigenschaften an. Der Vollständigkeit halber sind sie hier aufgeführt.
Typ-Eigenschaften
template <class T> struct is_const;
template <class T> struct is_volatile;
template <class T> struct is_trivial;
template <class T> struct is_trivially_copyable;
template <class T> struct is_standard_layout;
template <class T> struct is_empty;
template <class T> struct is_polymorphic;
template <class T> struct is_abstract;
template <class T> struct is_final;
template <class T> struct is_aggregate;
template <class T> struct is_signed;
template <class T> struct is_unsigned;
template <class T> struct is_bounded_array;
template <class T> struct is_unbounded_array;
template <class T> struct is_scoped_enum;
template <class T, class... Args> struct is_constructible;
template <class T> struct is_default_constructible;
template <class T> struct is_copy_constructible;
template <class T> struct is_move_constructible;
template <class T, class U> struct is_assignable;
template <class T> struct is_copy_assignable;
template <class T> struct is_move_assignable;
template <class T, class U> struct is_swappable_with;
template <class T> struct is_swappable;
template <class T> struct is_destructible;
template <class T, class... Args> struct is_trivially_constructible;
template <class T> struct is_trivially_default_constructible;
template <class T> struct is_trivially_copy_constructible;
template <class T> struct is_trivially_move_constructible;
template <class T, class U> struct is_trivially_assignable;
template <class T> struct is_trivially_copy_assignable;
template <class T> struct is_trivially_move_assignable;
template <class T> struct is_trivially_destructible;
template <class T, class... Args> struct is_nothrow_constructible;
template <class T> struct is_nothrow_default_constructible;
template <class T> struct is_nothrow_copy_constructible;
template <class T> struct is_nothrow_move_constructible;
template <class T, class U> struct is_nothrow_assignable;
template <class T> struct is_nothrow_copy_assignable;
template <class T> struct is_nothrow_move_assignable;
template <class T, class U> struct is_nothrow_swappable_with;
template <class T> struct is_nothrow_swappable;
template <class T> struct is_nothrow_destructible;
template <class T> struct has_virtual_destructor;
template <class T> struct has_unique_object_representations;
Viele der Metafunktionen wie std::is_trivially_copyable
haben "trivial" in ihrem Namen. Das bedeutet, dass der Compiler diese Methode bereitstellt. Eine Methode vom Compiler mit dem Schlüsselwort default anzufordern, ist ebenfalls trivial.
Typ-Eigenschaftsabfragen
template <class T> struct alignment_of;
template <class T> struct rank;
template <class T, unsigned I = 0> struct extent;
Wie geht's weiter?
Auffällig Ist die Funktion std::is_same
in der zusammengesetzten Typkategorie std::is_fundamental:
std::is_same
ist etwas Besonderes, weil sie Typvergleiche zur Compiletime ermöglicht. Darüber werde ich in meinem nächsten Artikel schreiben.
( [2])
URL dieses Artikels:
https://www.heise.de/-6273014
Links in diesem Artikel:
[1] https://heise.de/-6237233
[2] mailto:rainer@grimm-jaud.de
Copyright © 2021 Heise Medien