Biblioteka klas cech typów - <type_traits>¶
Biblioteka standardowa zawiera zestaw szablonów klas umożliwiających cechowanie typów na etapie kompilacji. Dzięki cechom typów (type traits) na etapie kompilacji:
- wybierana jest optymalna (wydajna) implementacja danej funkcjonalności (z wykorzystaniem SFINAE).
- przeprowadzana jest statyczna asercja umożliwiająca wczesne wychwycenie błędów
Meta-funkcje¶
Technika cechowania wykorzystuje meta-funkcje, najczęściej implementowane jako szablony klas przyjmujące jako parametr cechowany typ i zwracające:
type_trait<T>::type
- nowy typ będący efektem transformacji (np. usunięcia modyfikatoraconst
)type_trait<T>::value
- stałą statyczną wartośćconstexpr
(najczęściej typubool
)
Meta-funkcje zwracające wartość¶
Przykład implementacji meta-funkcji zwracającej stałą wartość ewaluowaną na etapie kompilacji:
template<typename T>
struct TypeSize
{
static constexpr std::size_t value = sizeof(T);
};
Wykorzystanie meta-funkcji:
std::cout << TypeSize<int>::value << std::endl;
Meta-funkcje zwracające typy¶
Przy implementacji meta-funkcji często stosujemy specjalizację szablonów:
// generic implementation for containers
template<typename C>
struct ElementT
{
using type = typename C::value_type
;
//partial specialization for arrays of known bounds
template<typename T, std::size_t N>
struct ElementT<T[N]>
{
using type = T;
};
//partial specialization for arrays of unknown bounds
template<typename T>
struct ElementT<T[]>
{
using type = T;
};
Meta-funkcje mogą być wykorzystane w innych szablonach:
template <typename Container>
void process(const Container& c)
{
typename ElementT<Container>::type item{};
//...
}
Stałe całkowite - integral constants¶
Szablon std::integral_constant
pozwala opakować w w postaci
meta-funkcji stałą zdefiniowaną na etapie kompilacji.
template<class T, T v>
struct integral_constant
{
static constexpr T value = v;
typedef T value_type;
typedef integral_constant type; // using injected-class-name
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; } //since c++14
};
Wykorzystanie meta-funkcji integral_constant
wygląda następująco:
integral_constant<int, 5>::value;
(const int) 5
Biblioteka standardowa definiuje pomocniczy alias dla stałej typu bool
:
template <bool v>
using bool_constant = integral_constant<bool, v>;
Zdefiniowane są również dwa typowe przypadki takich stałych:
using true_type = bool_constant<true>; // integral_constant<bool, true>
using false_type = bool_constant<false>; // integral_constant<bool, false>
Klasy cech typów¶
Klasy cech transformujące typy¶
Często w trakcie pisania kodu szablonowego zachodzi potrzeba transformacji typu określonego
parametru szablonu (np. wymagane jest usunięcie lub dodanie referencji, modyfikatora const
lub volatile
, itp.).
W takim przypadku możemy posłużyć się klasą cechy transformującej (implementowaną jako meta-funkcja):
template <typename T>
struct remove_reference
{
using type = T;
};
template <typename T>
struct remove_reference<T&>
{
using type = T;
};
template <typename T>
struct remove_reference<T&&>
{
using type = T;
};
Od C++14 do klas cech dodane są odpowiednie aliasy szablonów, które umożliwiają uniknięcie konieczności deklaracji typename
przed zagnieżdżonym typem type
:
template <typename T>
using remove_reference_t = typename remove_reference<T>::type;
Użycie cech transformujących wygląda następująco:
auto closure = [](auto&& item) {
remove_reference_t<decltype(item)> item_cpy = item;
};
Cechy transformujące typy w bibliotece standardowej¶
Biblioteka standardowa w nagłówku <type_traits>
definiuje zbiór klas cech transformujących:
Cecha | Rezultat ::value |
---|---|
remove_reference |
usuwa referencję z typu (int& -> int ) |
add_lvalue_reference |
dodaje lvalue referencję (double -> double& ) |
add_rvalue_reference |
dodaje rvalue referencję (double -> double&& ) |
remove_pointer |
usuwa wskaźnik z typu (int* -> int ) |
add_pointer |
dodaje wskaźnik (int -> int* ) |
remove_const |
usuwa modyfikator const (const int& -> int& ) |
remove_volatile |
usuwa modyfikator volatile (volatile int -> int ) |
remove_cv |
usuwa modyfikatory const i volatile |
add_const |
dodaje modyfikator const (int -> const int ) |
add_volatile |
dodaje modyfikator volatile (double -> volatile double ) |
Cecha std::decay
¶
Przydatną cechą jest zdefiniowana w bibliotece standardowej cecha std::decay
.
Dokonuje ona transformacji odpowiadającej następującym przekształceniom:
- usuwane są referencje
- usuwane są modyfikatory
const
lubvolatile
- tablice konwertowane są do wskaźników
- funkcje konwertowane są do wskaźników do funkcji
template <typename T, typename U>
void check_decay()
{
static_assert(std::is_same_v<std::decay_t<T>, U>);
}
check_decay<int&, int>();
check_decay<const int&, int>();
check_decay<int&&, int>();
check_decay<int(int), int(*)(int)>();
check_decay<int[20], int*>();
check_decay<const int[20], const int*>();
Klasy cech - predykaty¶
Implementacja cech typów, które pełnią rolę predykatów, polega zwykle na zdefiniowaniu ogólnego
szablonu dziedziczącego po false_type
(dla typów nie posiadających
określonej cechy). Kolejnym krokiem jest dostarczenie wersji
specjalizowanej szablonu dla typów z cechą, która dziedziczy po typie
true_type
.
Specjalizacja szablonu może być całkowita:
template <typename T>
struct IsVoid : std::false_type
{};
template <>
struct IsVoid<void> : std::true_type
{};
IsVoid<int>::value;
(const bool) false
IsVoid<void>::value;
(const bool) true
lub częściowa:
template <typename T>
struct IsPointer : std::false_type{};
template <typename T>
struct IsPointer<T*> : std::true_type{};
IsPointer<int>::value;
(const bool) false
IsPointer<const int*>::value;
(const bool) true
Standardowe cechy typów - predykaty¶
Biblioteka standardowa definiuje szeroki zbiór meta-funkcji, które umożliwiają odpytanie na etapie kompilacji, czy dany typ posiada odpowiednie cechy.
Cechy podstawowe¶
Cecha podstawowa | Rezultat ::value |
---|---|
is_array<T> |
true jeśli T jest typem tablicowym |
is_class<T> |
true jeśli T jest klasą |
is_enum<T> |
true jeśli T jest typem wyliczeniowym |
is_floating_point<T> |
true jeśli T jest typem zmiennoprzecinkowym |
is_function<T> |
true jeśli T jest funkcją |
is_integral<T> |
true jeśli T jest typem całkowitym |
is_member_object_pointer<T> |
true jeśli T jest wskaźnikiem do składowej |
is_member_function_pointer<T> |
true jeśli T jest wskaźnikiem do funkcji składowej |
is_pointer<T> |
true jeśli T jest typem wskaźnikowym
(ale nie wskaźnikiem do składowej) |
is_lvalue_reference<T> |
true jeśli T jest referencją do l-value |
is_rvalue_reference<T> |
true jeśli T jest referencją do r-value |
is_union<T> |
true jeśli T jest unią
(bez wsparcia kompilatora zawsze zwraca false |
is_void<T> |
true jeśli T jest typu void |
is_null_pointer<T> |
true jeśli T jest typu std::nullptr_t |
Cechy kompozytowe¶
Cechy kompozytowe są kompozycją najczęściej kilku cech podstawowych.
Cecha grupowana | Rezultat ::value |
---|---|
is_arithmetic<T> |
is_integral<T>::value ||
is_floating_point<T>::value |
is_fundamental<T> |
is_arithmetic<T>::value || is_void<T>::value || is_null_pointer<T>::value |
is_compound<T> |
!is_fundamental<T>::value |
is_object<T> |
is_scalar<T>::value || is_array<T>::value || is_union<T>::value ||
is_class<T>::value |
is_reference<T> |
is_lvalue_reference<T> || is_rvalue_reference<T> |
is_member_pointer<T> |
is_member_object_pointer<T> || is_member_function_pointer<T> |
is_scalar<T> |
is_arithmetic<T>::value || is_enum<T>::value || is_null_pointer<T>::value
is_pointer<T>::value || is_member_pointer<T>::value |
Właściwości typów¶
Standard używa terminu właściwość typu w celu zdefiniowania cechy opisującej wybrane atrybuty typu.
Wybrane właściwości typu:
Właściwość typu | Rezultat ::value |
---|---|
is_const<T> |
true jeśli T jest typem const |
is_volatile<T> |
true jeśli T jest typem ulotnym |
is_polymorphic<T> |
true jeśli T posiada przynajmniej jedną
funkcję wirtualną |
is_trivial<T> |
true jeśli T jest typem trywialnym |
is_trivially_copyable<T> |
true jeśli T jest trywialnie kopiowalne |
is_standard_layout<T> |
true jeśli T jest typem o standardowym
layout’cie |
is_pod<T> |
true jeśli T jest typem POD |
is_abstract<T> |
true jeśli T jest typem abstrakcyjnym |
is_unsigned<T> |
true jeśli T jest typem całkowitym bez znaku
lub typem wyliczeniowym zdefiniowanym przy pomocy
typu unsigned |
is_signed<T> |
true jeśli T jest typem całkowitym ze
znakiem lub typem wyliczeniowym zdefiniowanym przy
pomocy typu signed |
Cechy typów i statyczne asercje¶
Jednym z podstawowych zastosowań cech typów jest wykorzystanie ich do statycznych asercji w kodzie. Takie asercje nie pozwalają wygenerować błędnego kodu i jednocześnie podnoszą czytelność komunikatów o błędach w szablonach.
Przykład:
template <class T>
void swap(T& a, T& b)
{
static_assert(std::is_copy_constructible<T>::value,
"Swap requires copying");
static_assert(noexcept(std::is_nothrow_move_constructible<T>::value
&& std::is_nothrow_move_assignable<T>::value),
"Swap may throw");
auto c = b;
b = a;
a = c;
}