C++ Traits

Introduction

C++ traits is a special case of template programming which usually only focus on type specific features, such as static attributes and static functions. It is one of the most fundamental building blocks for C++ template programming.

In this blog post, I would like to quickly discuss what C++ traits is and how it contributes to C++ template programming.

C++ Traits

C++ traits can be used for unifying the low-level function interface for different types, making the high-level template programming simpler.

For example, if we want to create functions, say greater_than_half_maximum, for different numerical types, including int32_t, int64_t, float, double, that use the maximum value of the type, without using traits, we can hardly take the advantage of template programming.

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
#include <cfloat>

template <typename T>
bool greater_than_half_maximum(T value)
{
return false;
}

template <>
bool greater_than_half_maximum<int32_t>(int32_t value)
{
// 2^31 - 1
if (value > 2147483647 / 2)
{
return true;
}
else
{
return false;
}
}

template <>
bool greater_than_half_maximum<int64_t>(int64_t value)
{
// 2^63 - 1
if (value > 9223372036854775807 / 2)
{
return true;
}
else
{
return false;
}
}

template <>
bool greater_than_half_maximum<float>(float value)
{
// FLT_MAX defined in cfloat
if (value > FLT_MAX / 2)
{
return true;
}
else
{
return false;
}
}

template <>
bool greater_than_half_maximum<double>(double value)
{
// DBL_MAX defined in cfloat
if (value > DBL_MAX / 2)
{
return true;
}
else
{
return false;
}
}

// Even more specializations for different types.
// ...

From the above example, we could see that even if we wanted to use template programming, without using traits, the specialization implementations for different types is inevitable.

std::numeric_limits is a type trait from the C++ standard library which defines the maximum value for different built-in types. By using std::numeric_limits<T>::max(), we don’t need to create specializations for different types for our particular use case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <limits>

template <typename T>
bool greater_than_half_maximum(T value)
{
if (value > std::numeric_limits<T>::max() / 2)
{
return true;
}
else
{
return false;
}
}

For custom numerical types, such as NVIDIA __half, we could create specializations for std::numeric_limits instead of for greater_than_half_maximum.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <cstdint>
#include <limits>

namespace std
{
template <>
class numeric_limits<__half>
{
public:
constexpr static __half max()
{
constexpr uint16_t const half_max_bits{0x7BFF};
__half const half_max{*reinterpret_cast<__half const*>(&half_max_bits)};
return half_max;
};
};
} // namespace std

Of course, one could argue that for $n$ different types, we would have to create $n$ different type traits which is also a lot of work. However, because usually the low-level type traits are more reusable than the high-level specializations, it is more valuable to spend effort on creating low-level type traits than high-level template specializations. In addition, because high-level template specialization usually requires more lines of implementation than type traits, it takes less effort on creating low-level type traits than high-level template specializations.

C++ Traits Contributing to Template Metaprogramming

C++ type traits are critical for template metaprogramming. We will see how it is used for both function template metaprogramming and class template metaprogramming.

Function Template Metaprogramming

When the type traits were used with std::enable_if, we could specialize the function implementation details for different types, as described in my previous blog post “C++ Template Specialization Using Enable If”.

Class Template Metaprogramming

When the type traits were used with std::conditional_t, we could specialize the class implementation details for classes that have different types of member variables that cannot be simply templated.

For example, the C++ <random> library has two functions for generating random numbers from a uniform distribution, including the std::uniform_int_distribution for generating integer values and std::uniform_real_distribution for generating real values.

The following implementation unified the two random number generation functions, std::uniform_int_distribution and std::uniform_real_distribution, using type trait std::is_integral<T>::value.

random_number_generator.cpp
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
#include <iostream>
#include <iterator>
#include <random>
#include <vector>

template <typename T>
class UniformDist
{
public:
UniformDist(T a, T b) : m_min{a}, m_max{b}, m_uniform_dist{a, b} {}
T operator()(std::mt19937& random_engine)
{
return m_uniform_dist(random_engine);
}

private:
T const m_min;
T const m_max;
using dist_t = std::conditional_t<std::is_integral<T>::value,
std::uniform_int_distribution<T>,
std::uniform_real_distribution<T>>;
dist_t m_uniform_dist;
};

template <typename T>
std::vector<T> create_random_vector(size_t n, T a, T b,
std::mt19937& random_engine)
{
UniformDist<T> uniform_dist{a, b};
std::vector<T> vec(n, 0.0);
for (size_t i{0}; i < n; ++i)
{
vec[i] = uniform_dist(random_engine);
}
return vec;
}

int main()
{
size_t const n{8};
unsigned int const seed{0U};
std::mt19937 random_engine{seed};
std::vector<double> const random_vector_double{
create_random_vector(n, -16.0, 16.0, random_engine)};
std::cout << "Random Vector of Doubles: " << std::endl;
std::copy(random_vector_double.begin(), random_vector_double.end(),
std::ostream_iterator<double>(std::cout, " "));
std::cout << std::endl;
std::vector<int> const random_vector_int{
create_random_vector(n, -16, 16, random_engine)};
std::cout << "Random Vector of Integers: " << std::endl;
std::copy(random_vector_int.begin(), random_vector_int.end(),
std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
}
1
2
3
4
5
6
$ g++ random_number_generator.cpp -o random_number_generator -std=c++14
$ ./random_number_generator
Random Vector of Doubles:
2.97103 11.0165 11.4543 11.1121 3.95404 -3.69979 -6.47889 -14.1852
Random Vector of Integers:
15 -8 -4 -1 10 10 1 -1

References

Author

Lei Mao

Posted on

03-07-2023

Updated on

03-07-2023

Licensed under


Comments