Template Metaprogrammierung ist die Programmierung zur Compilezeit. Was hat das ganze mit der Type-Traits Bibliothek zu tun? Ganz viel. Die Type-Traits Bibliothek ist Template Metaprogrammierung, die in einer Bibliothek gezähmt wurde. Mit diesem Artikel kommt mehr Ordnung in meine Vorstellung der Type-Traits Bibliothek.
Typeigenschaften abfragen
Die Type-Traits Bibliothek kennt primäre und zusammengesetzte Typkategorien. Diese lassen sich mit dem Attribut value abfragen.
Primäre Typkategorien
C++ besitzt 14 primäre Typkategorien. Diese sind vollständig, schließen sich gegenseitig aus, so dass ein Typ in genau einer Typkategorie ist. Die Abfrage der primären Typkategorie ist unabhängig davon, ob der Typ const oder volatile ist.
Die 14 primären Typkategorien:
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_reference;
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;
Nun fehlt nur noch deren Anwendung:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
// 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(){
std::cout << std::boolalpha << std::endl;
std::cout << std::is_void<void>::value << std::endl;
std::cout << std::is_integral<short>::value << std::endl;
std::cout << std::is_floating_point<double>::value << std::endl;
std::cout << std::is_array<int [] >::value << std::endl;
std::cout << std::is_pointer<int*>::value << std::endl;
std::cout << std::is_reference<int&>::value << std::endl;
std::cout << std::is_member_object_pointer<int A::*>::value << std::endl;
std::cout << std::is_member_function_pointer<int (A::*)(int)>::value << std::endl;
std::cout << std::is_enum<E>::value << std::endl;
std::cout << std::is_union<U>::value << std::endl;
std::cout << std::is_class<std::string>::value << std::endl;
std::cout << std::is_function<int * (double)>::value << std::endl;
std::cout << std::is_lvalue_reference<int&>::value << std::endl;
std::cout << std::is_rvalue_reference<int&&>::value << std::endl;
std::cout << std::endl;
}
|
Durch Verwendung des Flags std::boolalpha in Zeile 22 gibt das Programm anstelle von 1 oder 0, true oder false aus. Jeder Aufruf der 14 primären Typkategorien ergibt true.
Im Wesentlichen basiert alles auf Template und deren Spezialisierung, ein paar Konventionen und viel Fleißarbeit. Eine mögliche Implementierung des Funktions-Templates std::integral, das bestimmt, ob eine Typ integral ist, habe ich im folgenden Beispiel implementiert.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
// 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; }
constexpr value_type operator()() const noexcept { return value; } //since c++14
};
typedef integral_constant<bool, true> true_type;
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{};
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 << std::endl;
std::cout << "std::is_integral<int>::value: " << std::is_integral<int>::value << std::endl;
std::cout << "rgr::is_integral<int>::value: " << rgr::is_integral<int>::value << std::endl;
std::cout << "std::is_integral<double>::value: " << std::is_integral<double>::value << std::endl;
std::cout << "rgr::is_integral<double>::value: " << rgr::is_integral<double>::value << std::endl;
std::cout << std::endl;
std::cout << "std::true_type::value: " << std::true_type::value << std::endl;
std::cout << "rgr::true_type::value: " << rgr::true_type::value << std::endl;
std::cout << "std::false_type::value: " << std::false_type::value << std::endl;
std::cout << "rgr::false_type::value: " << rgr::false_type::value << std::endl;
std::cout << std::endl;
std::cout << "std::integral_constant<bool, true>::value: " << std::integral_constant<bool, true>::value << std::endl;
std::cout << "rgr::integral_constant<bool, true>::value: " << rgr::integral_constant<bool, true>::value << std::endl;
std::cout << "std::integral_constant<bool, false>::value: " << std::integral_constant<bool, false>::value << std::endl;
std::cout << "rgr::integral_constant<bool, false>::value: " << rgr::integral_constant<bool, false>::value << std::endl;
std::cout << std::endl;
}
|
Zur besseren Unterscheidung verwende ich für meine Implementierung den Namensraum rgr und stelle ihrer Anwendung in der main-Funktion der Funktionalität der Type-Traits Bibliothek im Namensraum std gegenüber. Wird nun in der Zeile 69 rgr::is_integral<int>::value aufgerufen, führt dies dazu, das unter der Decke rgr::true_type::value in Zeile 77 zur Anwendung kommt, da is_integral<int> von true_type abgeleitet ist (Zeile 42). rgr::true_type::value wiederum ist nur ein Alias für rgr::integral_constant<bool, true>::value (Zeile 17).In dem konkreten Beispiel kommt nur der Wert value der statischen Konstanten integral_constant zum Einsatz. Diese statische Konstante ist die Basis-Klasse für die Type-Traits Bibliothek.
Zum Abschluß folgt noch die Ausgabe. Meine Implementierung und die Implementierung der Type-Traits Bibliothek verhalten sich gleich.
Basierend auf den 14 primären Typkategorien bietet C++ 7 zusammengesetzte Typkategorien an.
Zusammengesetzte Typkategorien
Die is_fundamental Typkategorie verwendet das Funktions-Template is_same. Dazu mehr im nächsten Artikel zu den Typvergleichen mit der Type-Traits Bibliothek.
Typeigenschaften
Neben den primären und zusammengesetzten Typkategorien bieten Typeigenschaften den Zugang zu weiteren, wichtigen Typinformationen.
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_pod;
template <class T> struct is_literal_type;
template <class T> struct is_empty;
template <class T> struct is_polymorphic;
template <class T> struct is_abstract;
template <class T> struct is_signed;
template <class T> struct is_unsigned;
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> 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> struct is_nothrow_destructible;
template <class T> struct has_virtual_destructor;
Viele der Funktions-Template wie is_trivially_copyable besitzen den Namensbestandteil trivially. Das heißt, das die entsprechende Methode vom Compiler automatisch bei Bedarf erzeugt und nicht vom Programmierer überladen werden darf. Methoden, die der Programmierer mittels dem Bezeichner default explizit vom Compiler anfordert, sind trivial.
Wie geht's weiter?
Die Type-Traits Bibliothek hat einiges zu bieten. Im nächsten Artikel werden Typvergleiche und Typmodifikationen zu Compilezeit mein Topic sein.
Was du schon immer wissen wolltest
- default
- Im Artikel Automatik mit Methode 08/2014 für das Linux-Magazin gehe ich genauer auf vom Compiler automatisch erzeugte Methoden ein.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...