Klasy cech typów - biblioteka <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>::value- stałą statyczną wartośćconstexpr(najczęściej typubool)type_trait<T>::type- nowy typ będący efektem transformacji (np. usunięcia modyfikatoraconst)
Meta-funkcje zwracające wartość#
Przykład implementacji meta-funkcji zwracającej stałą wartość ewaluowaną na etapie kompilacji:
template <typename T, T Constant>
struct IntegralConstant
{
static constexpr T value = Constant;
};
static_assert(IntegralConstant<int, 5>::value == 5);
static_assert(IntegralConstant<char, 'a'>::value == 'a');
static_assert(IntegralConstant<bool, true>::value == true);
Dla takich meta-funkcji często definiujemy szablony zmiennych, które pozwalają uniknąć konieczności deklaracji ::value:
template <typename T, T Constant>
inline constexpr T IntegralConstant_v = IntegralConstant<T, Constant>::value;
static_assert(IntegralConstant_v<int, 5> == 5);
static_assert(IntegralConstant_v<char, 'a'> == 'a');
static_assert(IntegralConstant_v<bool, true> == true);
Meta-funkcje zwracające typy#
Najprostsz przykład meta-funkcji zwracającej typ to Identity:
template <typename T>
struct Identity
{
using type = T;
};
static_assert(std::is_same<Identity<int>::type, int>::value);
static_assert(std::is_same<Identity<std::string>::type, std::string>::value);
Dla takich meta-funkcji często definiujemy aliasy szablonów, które pozwalają uniknąć konieczności deklaracji typename
przed zagnieżdżonym typem type:
template <typename T>
using Identity_t = typename Identity<T>::type;
static_assert(std::is_same<Identity_t<int>, int>::value);
static_assert(std::is_same<Identity_t<std::string>, std::string>::value);
Klasy cech typów - predykaty#
Predykaty to meta-funkcje, które zwracają wartość true lub false w zależności od cechowanego typu.
Klasa cech IsVoid#
Najprostszym przykładem predykatu jest IsVoid, który sprawdza, czy dany typ to void. Implementacja predykatu polega na zdefiniowaniu ogólnego
szablonu, który definiuje zagnieżdżoną stałą statyczną value o wartości false, oraz pełnej specjalizacji szablonu dla typu void, która definiuje value jako true.
template <typename T>
struct IsVoid
{
static constexpr bool value = false;
};
template <>
struct IsVoid<void>
{
static constexpr bool value = true;
};
template <typename T>
inline constexpr bool IsVoid_v = IsVoid<T>::value;
static_assert(!IsVoid_v<int>); // int is not void - predicate is false
static_assert(IsVoid_v<void>); // void is void - predicate is true
Klasa cech IsSame<T, U>#
Predykat IsSame sprawdza, czy dwa typy są takie same. Implementacja predykatu polega na zdefiniowaniu ogólnego
szablonu, który definiuje zagnieżdżoną stałą statyczną value o wartości false, oraz specjalizacji częściowej szablonu dla dwóch takich samych typ
ów, która definiuje value jako true.
template <typename T, typename U>
struct IsSame
{
static constexpr bool value = false;
};
template <typename T>
struct IsSame<T, T>
{
static constexpr bool value = true;
};
template <typename T, typename U>
inline constexpr bool IsSame_v = IsSame<T, U>::value;
static_assert(IsSame_v<int, int>); // true
static_assert(!IsSame_v<int, unsigned>); // false
static_assert(IsSame_v<std::string, std::string>); // true
static_assert(!IsSame_v<std::string, const char*>); // false
Note
Predykatową klasę cech można zaimplementować dziedzicząc po std::true_type i std::false_type, które są zdefiniowane w bibliotece standardowej w nagłówku <type_traits>.
template <typename T, typename U>
struct IsSame : std::false_type
{};
template <typename T>
struct IsSame<T, T> : std::true_type
{};
### 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):
```cpp
template <typename T>
struct RemoveReference
{
using type = T;
};
template <typename T>
struct RemoveReference<T&>
{
using type = T;
};
template <typename T>
struct RemoveReference<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 RemoveReference_t = typename RemoveReference<T>::type;
Klasa cechy RemoveReference może być wykorzystana w innych szablonach, np. do implementacji funkcji move:
template <typename T>
RemoveReference_t<T>&& move(T&& item)
{
return static_cast<RemoveReference_t<T>&&>(item);
}
Biblioteka standardowa <type_traits>#
Cechy transformujące typy w bibliotece standardowej#
Biblioteka standardowa w nagłówku <type_traits> definiuje zbiór klas cech transformujących:
Cecha |
Rezultat |
|---|---|
|
usuwa referencję z typu ( |
|
dodaje lvalue referencję ( |
|
dodaje rvalue referencję ( |
|
usuwa wskaźnik z typu ( |
|
dodaje wskaźnik ( |
|
usuwa modyfikator |
|
usuwa modyfikator |
|
usuwa modyfikatory |
|
dodaje modyfikator |
|
dodaje modyfikator |
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
constlubvolatiletablice 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*>();
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Cechy kompozytowe#
Cechy kompozytowe są kompozycją najczęściej kilku cech podstawowych.
Cecha grupowana |
Rezultat |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
}
Przykłady użycia cech typów#
Załóżmy, że chcemy napisać szablon funkcji process, który będzie przetwarzał elementy kontenera. Aby to zrobić, musimy znać typ elementów przechowywanych w kontenerze. Możemy to osiągnąć za pomocą klasy cechy ValueType, która zwraca typ elementów kontenera.
// generic implementation for containers
template<typename Container>
struct ValueType
{
using type = typename Container::value_type;
};
//partial specialization for arrays of known bounds
template<typename T, std::size_t N>
struct ValueType<T[N]>
{
using type = T;
};
//partial specialization for arrays of unknown bounds
template<typename T>
struct ValueType<T[]>
{
using type = T;
};
template <typename Container>
using ValueType_t = typename ValueType<Container>::type;
Meta-funkcje mogą być wykorzystane w innych szablonach:
template <typename Container>
void process(const Container& c)
{
ValueType_t<Container> item{};
//...
}