C++ Type Traits
A type trait answers a yes or no question about a type. The answer is fixed while the program is being built.
Trait verdicts for three types
The check function prints two verdicts for its type. The int call, the double call, and the string call give three different rows.
#include <iostream>
#include <string>
#include <type_traits>
template <typename T>
void check(T value) {
(void)value;
std::cout << std::is_integral_v<T> << " " << std::is_floating_point_v<T> << std::endl;
}
int main() {
std::cout << std::boolalpha;
check(7);
check(2.5);
check(std::string("hi"));
return 0;
}Traits steer an if constexpr branch
The describe function tests its type with a trait inside if constexpr. The int call prints its value and the double call prints the other line.
#include <iostream>
#include <type_traits>
template <typename T>
void describe(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Whole number: " << value << std::endl;
} else {
std::cout << "Something else" << std::endl;
}
}
int main() {
describe(7);
describe(2.5);
return 0;
}conditional_t picks the counter type
The nextCount function returns an int for a whole number start and a double for a decimal start. The verdicts beside the values prove which type each call picked.
#include <iostream>
#include <type_traits>
template <typename T>
std::conditional_t<std::is_integral_v<T>, int, double> nextCount(T start) {
using Picked = std::conditional_t<std::is_integral_v<T>, int, double>;
Picked counter = static_cast<Picked>(start);
counter = counter + 1;
return counter;
}
int main() {
auto first = nextCount(41);
auto second = nextCount(1.5);
std::cout << std::boolalpha;
std::cout << first << " " << std::is_same_v<decltype(first), int> << std::endl;
std::cout << second << " " << std::is_same_v<decltype(second), double> << std::endl;
return 0;
}A trait chain cleans a type
The chain first drops the reference part from int& and then cleans the result. The verdicts show the cleaned type is int and is not const int.
#include <iostream>
#include <type_traits>
int main() {
using Clean = std::decay_t<std::remove_reference_t<int&>>;
std::cout << std::boolalpha;
std::cout << std::is_same_v<Clean, int> << " " << std::is_same_v<Clean, const int> << std::endl;
return 0;
}Example
#include <iostream>
#include <string>
#include <type_traits>
template <typename T>
void describe(T value) {
if constexpr (std::is_integral_v<T>) {
std::cout << "Whole number: " << value << std::endl;
} else if constexpr (std::is_floating_point_v<T>) {
std::cout << "Decimal number: " << value << std::endl;
} else {
std::cout << "Text value: " << value << std::endl;
}
}
int main() {
describe(7);
describe(2.5);
describe(std::string("hello"));
return 0;
}Key points
- A type trait answers a yes or no question about a type.
- std::is_integral_v is true when the type is a whole number type.
- std::is_floating_point_v is true when the type holds a decimal value.
- The _v ending names the value form of a trait.
- if constexpr can pick a branch from a trait verdict.
- std::conditional_t picks one of two types from a condition.
- std::remove_reference_t drops the reference part from a type.
In this example, describe tests its type with two traits in order. The int call takes the whole number branch and prints 7. The double call takes the decimal branch and the string call falls through to the text branch.
Note
A trait verdict is fixed while the program is being built and never changes later.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
What does std::conditional_t do?