This is my learning notes from the course - Mastering C++ Standard Library Features.
<type_traits>
header
std::conditional
std::integral_constant
std::conjunction
and std::disjunction
<utilities>
header
std::integer_sequence
std::make_integer_sequence
< type_traits > - Overview
Example: Using to Assert Type Properties
1 2 3 4 5 6 7
| template <typename T> void store(T&& x) { static_assert(!std::is_polymorphic_v<T>, "`T` must not be a polymorphic type");
}
|
- Produces a clear error message and prevents mistakes
Example: Using to Constrain Overloads
1 2 3 4 5 6 7 8 9 10 11
| template <typename T> auto store(T&& x) -> std::enable_if_t<std::is_polymorphic_v<T>> { }
template <typename T> auto store(T&& x) -> std::enable_if_t<!std::is_polymorphic_v<T>> { }
|
Example: Using with if constexpr
1 2 3 4 5 6 7 8 9 10 11 12
| template <typename T> void store(T&& x) { if constexpr(std::is_polymorphic_v<T>) { } else { } }
|
Properties and Predicates
- Useful to assert type properties and produce better error messages (that is using
static_assert
)
- Useful to constrain overloads depending on type properties
- In C++11/14, use
std::enable_if
- In C++17, consider using
if constexpr(...)
std::conditional
std::conditional<Predicate,T,F>
evaluates to:
T
if Predicate == true
T
if Predicate == false
- This can be used to choose a type depending on a compile-time condition or type property
Example: std::conditional Basic Usage
1 2 3 4
| template <typename T> using container_for = std::conditional_t< sizeof(T) <= 64, std::vector<T>, std::list<T> >;
|
1 2
| static_assert(std::is_same_v<container_for<int>, std::vector<int>>); static_assert(std::is_same_v<container_for<huge_obj>, std::vector<huge>>);
|
Example: Wrapping a T& with std::reference_wrapper
1 2 3 4 5
| template <typename T> struct items { std::vector<T> _data; };
|
1 2
| items<int> i0; items<int&> i1;
|
std::vector<T&>
is invalid, as T&
is not Assignable
1 2 3 4 5 6 7 8 9
| template <template T> struct items { using item_type = std::conditional_t< std::is_reference_v<T>, std::reference_wrapper<std::remove_reference_t<T>>, T>; std::vector<item_type> _data; };
|
1 2
| items<int> i0; items<int&> i1;
|
- No need to specialize
items
in its entirety
std::disjunction and std::conjunction
- Introduced in C++17, these utilities allow you to perform a logical OR/AND on a sequence of traits
std::disjunction<Ts...>
-> logical OR
std::conjunction<Ts...>
-> logical AND
- These utilities are short-circuiting: if a result is known, the
::value
of the rest of the passed traits will not be instantiated
- In contrast, C++17 fold expressions do not short-circuit
Example: Any/All Trait Satisfaction
1 2 3 4 5
| template <typename T, template <typename> typename ... Traits> using satisfies_all = std::conjunction<Traits<T> ...>;
template <typename T, template <typename> typename ... Traits> using satisfies_any = std::disjunction<Traits<T> ...>;
|
1 2 3
| template <typename T> void foo() -> std::enable_if_t<satisfies_all<T, std::is_pointer, std::is_const>{}> { }
|
std::integral_constant
std::integral_constant
is defined as follows:
1 2 3 4 5 6 7 8 9 10 11
| template <typename T, T Value> struct integral_constant { static constexpr T value = Value;
using value_type = T; using type = integral_constant;
constexpr operator value_type() const noexcept; constexpr value_type operator()() const noexcept; };
|
std::integral_constant
allows you to encode a value in a type:
1 2
| using five = std::integral_constant<int, 5>; static_assert(five{} == 5);
|
Five can be instantiated and passed around to templates as an empty value. The constant can be recovered thanks to the constexpr
conversions:
1 2 3 4 5
| template <typename T> void foo(T x) { static_assert(x == 5); }
|
1 2
| using five = std::integral_constant<int, 5>; foo(five{});
|
To recap,
1 2 3 4 5
| void foo(int x) { static_assert(x == 10); } foo(10);
|
1 2 3 4 5 6
| template <typename T> void foo(T x) { static_assert(x == 10); } foo(std::integral_constant<int, 10>{});
|
std::integral_constant
is an example of the type-value encoding idiom (a.k.a. “dependent typing”)
- It blurs the line between types and values:
- It encodes a numerical value as part of its type
- It can be passed around as a value, allowing compile-time retrieval of the stored constant
- Often used for tag dispatching
- The Standard Library provides common aliases:
std::bool_constant<B>
(since C++17)
std::true_type
std::false_type
Example: Tag Dispatching
1 2 3 4 5 6 7 8
| template <typename T> void store_impl(T&& x, std::true_type ) { }
template <typename T> void store_impl(T&& x, std::false_type ) { this->storage_for<T>().add(std::forward<T>(x)); }
|
1 2 3 4
| template <typename T> void store(T&& x) { store_impl(std::forward<T>(x), std::is_empty<T>{}); }
|
std::integer_sequence
std::integer_sequence<T, IS...>
represents a compile-time sequence of integers
std::index_sequence
is an alias for std::size_t
integer sequences
1 2 3
| using s0 - std::integer_sequence<int, 0, 5, 2>; using s0 - std::integer_sequence<int>; using s0 - std::index_sequence<5, 4, 3, 2, 1>;
|
It allows you to “package” an arbitrary amount of integers and “match” them in a template function call
1 2 3 4 5 6
| template <std::size_t... Is> void foo(std::index_sequence<Is...>) { bar(some_array[Is]...); } foo(std::index_sequence<0, 1, 2, 3>{});
|
- Common use case: generating sequences from 0 to N-1
- The Standard Library provides:
std::make_integer_sequence<T, N>
std::make_index_sequence<N>
1 2
| using s0 = std::make_index_sequence<5>; static_assert(std::is_same_v<s0, std::index_sequence<0, 1, 2, 3, 4>>);
|
Example: Invoking a Function with Elements of std::array
Firstly, let us define an apply_array
function that creates an index_sequence
and forwards everything to an implementation:
1 2 3 4 5 6 7 8 9
| template <typename F, typename Array> decltype(auto) apply_array(F&& f, Array&& a) { return apply_array_impl( std::forward<F>(f), std::forward<Array>(a), std::make_index_sequence<a.size()>{} ); }
|
The implementation will “match” the index sequence (Is) indices and use pack expansion inside a function call to f:
1 2 3 4 5
| template <typename F, typename Array, std::size_t... Is> decltype(auto) apply_array_impl(F&& f, Array&& a, std::index_sequence<Is...>) { return std::forward<F>(f)(a[Is]...); }
|
1 2 3 4
| std::array<int, 5> a{3, 6, 1, 2, 4}; auto sum = apply_array([](auto... xs){ return (xs + ...); }, a);
assert(sum == 3 + 6 + 1 + 2 + 4);
|
Example: Compile-Time Iteration Over Integers
Goal: Have a repeat(f) function that invokes f
N times, passing the current index
1 2 3 4 5 6 7 8 9 10 11
| repeat<16>([](auto i) { if constexpr(i % 2 == 0) { std::cout << i << "is even\n"; } else { std::cout << i << "is odd\n"; } });
|
Firstly, let’s create the interface repeat function:
1 2 3 4 5
| template <std::size_t N, typename F> void repeat(F&& f) { repeat_impl(std::forward<F>(f), std::make_index_sequence<N>{}); }
|
Let us “match” the index_sequence
and use a fold expression to invoke f
with integral_constant
:
1 2 3 4 5
| template <typename F, std::size_t... Is> void repeat_impl(F&& f, std::index_sequence<Is...>) { ( f(std::integral_constant<std::size_t, Is>{}), ... ); }
|
Summary
<type_traits>
provides
std::conditional
std::integral_constant
std::conjunction
std::disjunction
- Utilities provides
std::integer_sequence
std::make_integer_sequence
std::conditional<B, T, F>
can be used to select a type depending on a compile-time condition
std::conjunction
and std::disjunction
can be used to compose traits in a short-circuiting manner
std::integral_constant
allows you to encode a value in a type, and then conveniently retrieve it later on
std::integer_sequence
and std::make_integer_sequence
allow you to create compile-time sequence of integers
Guidelines
- Use
std::conditional
when you need to change a small part of a class without completely specializing it
- Use
std::conjunction
and std::disjunction
to compose type traits together
- Consider using
std::integral_constant
as part of your interfaces to allow users to conveniently pass compile-time constants as values
- Use
std::integer_sequence
to expands collections like array and tuple at compile-time
Reference Link