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.

In Pocket speichern vorlesen Druckansicht 99 Kommentare lesen
Lesezeit: 8 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

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.

Jeder Typ gehört genau zu einer der vierzehn primären 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

}

Diese Technik basiert auf Templates und Template-Spezialisierung, ein paar Konventionen und einer Menge Tipparbeit. Ich habe eine vereinfachte Version des Funktions-Templates std::integral implementiert. std::integral 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.


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" erinnern: Meine Funktions-Templates rgr::is_integral 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.

Es gibt sieben zusammengesetzte Typkategorien. Die folgende Tabelle zeigt sie.

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.

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.

template <class T> struct alignment_of;
template <class T> struct rank;
template <class T, unsigned I = 0> struct extent;

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. ()