<type_traits> is a C++ header file that provides templates to check and manipulate types during compile time or It helps us ask questions about types (like "Is this an integer?") and make decisions before the program even runs.
To use type traits in the program include the type traits library.
#include <type_traits>
Syntax to Use type_traits
trait_name<T>::valueHere,
trait_nameis the name of the type trait you want to use.Tis the type you want to check.- The
::valuepart will give the result of the type trait for the given typeT.
Example
#include <iostream>
#include <type_traits>
// A template function that checks if T is an integer type
template <typename T>
void checkType() {
// Use if constexpr to make compile-time decision (C++17 and above)
if constexpr (std::is_integral<T>::value) {
std::cout << "T is an integer type.\n";
} else {
std::cout << "T is NOT an integer type.\n";
}
}
int main() {
checkType<int>();
checkType<float>();
checkType<char>();
return 0;
}
Output
T is an integer type. T is NOT an integer type. T is an integer type.
Categories of <type_traits> in C++
The type traits are classified into the following categories:
1. Unary Type Traits
"Unary" means one input type.
These traits check properties of a single type.
The following functions are used to check the properties of a single type of object:
- is_integral: is_integral checks if a type is an integral type.
- is_floating_point: is_floating_point checks if a type is a floating-point type (e.g., float, double).
- is_array: is_array checks if a type is an array.
- is_const: is_const checks if a type is a const-qualified type.
- is_reference: is_reference checks if a type is a reference type.
2. Binary Type Traits
"Binary" means two input types.
These traits compare two types.
The following functions are used to establish relationships between two objects:
- is_same: is_same compares if two types are exactly the same.
- is_convertible: is_convertible etermines if a value of one type can be converted to another.
3. Transformation Traits
These traits change or transform a type into another.
Use in templates to manipulate types safely.
The following functions are used to modify properties of a type:
- add_const: add_const adds the const qualifier to a type.
- add_volatile: add_volatile adds the volatile qualifier to a type.
- make_unsigned: make_unsigned converts a signed type to its unsigned counterpart (if applicable).
Applications of <type_traits> in C++
Conditional Compilation
One of the primary uses of <type_traits> is conditional compilation based on type properties. By conditional compilation we can ensure only a certain type of data is processed during the compilation of our program.
Example:
In the below example demonstrates the use of is_integral to ensure both arguments are of numeric types before performing the operations.
// C++ Program for conditional compiling
#include <iostream>
#include <type_traits>
using namespace std;
template <typename T1, typename T2>
// add integral check using type_traits
typename enable_if<is_integral<T1>::value
&& is_integral<T2>::value,
decltype(T1() + T2())>::type
add(T1 a, T2 b)
{
return a + b;
}
int main()
{
// This will compile as both arguments are of intergral
// type
int sum1 = add(5, 3);
cout << "Sum1 is: " << sum1;
// This will not compile as both arguments are not of
// floting point type
int sum2 = add(11.5, 12.5);
cout << "Sum2 is: " << sum1;
}
Output
Sum1 is: 8
error: no matching function for call to ‘add(double, double)’
19 | int sum2 = add(11.5,12.5);
Compile-Time Type Inspections
<type_traits> are also used to check the type of a data during the compilation of the program.
Example:
In the below example demonstrates the use of is_pointer, is_array, is_const to ensure the data types matches the expected type of data by the functions.
// C++ Program to check type of objects during compilation
#include <iostream>
#include <type_traits>
using namespace std;
// Function to check type properties
template <typename T>
void checkType(const T value)
{
// Check if type is const
if (is_const<T>::value) {
cout << "Type is const." << endl;
}
else {
cout << "Type is not const." << endl;
}
// Check if type is a pointer
if (is_pointer<T>::value) {
cout << "Type is a pointer." << endl;
}
else {
cout << "Type is not a pointer." << endl;
}
// Check if type is an array
if (is_array<T>::value) {
cout << "Type is an array." << endl;
}
else {
cout << "Type is not an array." << endl;
}
}
int main()
{
int x = 5;
// Declaring a const type
const int y = 10;
// Declaring a pointer type
int* ptr = &x;
// Declaring an array type
int arr[5];
// Checking type properties for variable x
cout << "Checking type for variable x:" << endl;
checkType(x);
cout << endl;
// Checking type properties for const variable y
cout << "Checking type for const variable y:" << endl;
checkType(y);
cout << endl;
// Checking type properties for pointer variable ptr
cout << "Checking type for pointer variable ptr:"
<< endl;
checkType(ptr);
cout << endl;
// Checking type properties for array variable arr
cout << "Checking type for array variable arr:" << endl;
checkType(arr);
cout << endl;
return 0;
}
Output
Checking type for variable x:
Type is not const.
Type is not a pointer.
Type is not an array.
Checking type for const variable y:
Type is not const.
Type is not a pointer.
Type is not an array.
Checking type for pointer variable ptr:
Type is not const.
Type is a pointer.
Type is not an array.
Checking type for array variable arr:
Type is not const.
Type is a pointer.
Type is not an array.
Substitution Failure is Not an Error(SFINAE)
It is a feature that lets the compiler silently ignore a template function or class if substituting a type makes it invalid.
This allows us to enable or disable functions based on type traits, without causing compilation errors.
Example:
// C++ program for SFINAE
#include <iostream>
#include <type_traits>
using namespace std;
// Function template for processing integral values
template <typename T, typename enable_if<
is_integral<T>::value, int>::type
= 0>
void process(T value)
{
cout << "Processing integral value: " << value << endl;
}
// Function template for processing floating-point values
template <typename T,
typename enable_if<is_floating_point<T>::value,
int>::type
= 0>
void process(T value)
{
cout << "Processing floating-point value: " << value
<< endl;
}
int main()
{
process(5);
process(3.14);
return 0;
}
Output
Processing integral value: 5 Processing floating-point value: 3.14
Compile Time Assertions
The <type_traits> header is also used for implementing compile time assertions in the program. This ensures that the code consists of only certain type of data allowed by the user or else it generates compile time errors. Function like is_arithmetic, is_array are used to restrict the type of arguments allowed by the user.
Example:
// C++ Program for compile time assertions
#include <iostream>
#include <type_traits>
using namespace std;
template <typename T>
// Function allowing arithmetic type only
void printValue(const T& value)
{
static_assert(is_arithmetic<T>::value,
"T must be an arithmetic type");
cout << "Value: " << value << endl;
}
int main()
{
// The following function calls will not cause an error
// as both are of artihmertic types
printValue(42);
printValue(3.14);
// this will generate a compile time error
printValue("hello");
return 0;
}
Output
error: static assertion failed: T must be an arithmetic type