Type Traits in C++ are a powerful mechanism introduced in the Standard Template Library (STL) that allows the examination and manipulation of types at compile time. They are implemented as template structures or classes and can be used to query properties about types, enabling compile-time decisions and optimizations. Type traits are a key part of template metaprogramming and are typically found in the <type_traits> header.
Some common examples of type traits include:
std::is_integral: Checks if a type is an integral type (like int, char, long, etc.).std::is_same: Checks if two types are exactly the same.std::is_floating_point: Checks if a type is a floating-point type (float, double, etc.).std::is_array, std::is_pointer, std::is_reference, etc.Type traits are primarily used in template metaprogramming to:
Here are some of the most commonly used type traits:
std::is_integral<T>: Determines if a type is an integral type.
#include <type_traits>
#include <iostream>
int main() {
std::cout << std::boolalpha;
std::cout << std::is_integral<int>::value << "\n"; // true
std::cout << std::is_integral<float>::value << "\n"; // false
}
std::is_same<T, U>: Determines if two types T and U are the same.
#include <type_traits>
#include <iostream>
int main() {
std::cout << std::boolalpha;
std::cout << std::is_same<int, int>::value << "\n"; // true
std::cout << std::is_same<int, double>::value << "\n"; // false
}
std::is_floating_point<T>: Checks if a type is a floating-point type (float, double, or long double).
#include <type_traits>
#include <iostream>
int main() {
std::cout << std::boolalpha;
std::cout << std::is_floating_point<float>::value << "\n"; // true
std::cout << std::is_floating_point<int>::value << "\n"; // false
}
std::is_pointer<T>: Determines if a type is a pointer.
#include <type_traits>
#include <iostream>
int main() {
std::cout << std::boolalpha;
std::cout << std::is_pointer<int*>::value << "\n"; // true
std::cout << std::is_pointer<int>::value << "\n"; // false
}
std::is_reference<T>: Determines if a type is a reference type (lvalue or rvalue reference).
#include <type_traits>
#include <iostream>
int main() {
std::cout << std::boolalpha;
std::cout << std::is_reference<int&>::value << "\n"; // true
std::cout << std::is_reference<int>::value << "\n"; // false
}
std::is_const<T>: Determines if a type is a const-qualified type.
#include <type_traits>
#include <iostream>
int main() {
std::cout << std::boolalpha;
std::cout << std::is_const<const int>::value << "\n"; // true
std::cout << std::is_const<int>::value << "\n"; // false
}
Type traits are typically implemented as template structs. They have a member ::value which is a constexpr bool that evaluates to true or false.
Here’s a simplified version of how std::is_integral might be implemented:
template<typename T>
struct is_integral {
static constexpr bool value = false;
};
template<>
struct is_integral<int> {
static constexpr bool value = true;
};
template<>
struct is_integral<char> {
static constexpr bool value = true;
};
// Other integral types would have similar specializations
The general case defaults to false, and specific specializations are provided for integral types like int and char to return true.
enable_if and SFINAEType traits are often used with std::enable_if and SFINAE to enable or disable functions or class templates based on the properties of types.
std::enable_if#include <type_traits>
#include <iostream>
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
add(T a, T b) {
return a + b;
}
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(2, 3) << std::endl; // Calls integral version
std::cout << add(2.5, 3.1) << std::endl; // Calls floating-point version
// std::cout << add("hello", "world") << std::endl; // Error, not integral or floating-point
}
In this example:
enable_if is used to enable the first add function only if T is an integral type and the second add function only if T is a floating-point type.#include <type_traits>
#include <iostream>
template <typename T>
void foo(T t, typename std::enable_if<std::is_integral<T>::value>::type* = 0) {
std::cout << "foo called with integral type" << std::endl;
}
template <typename T>
void foo(T t, typename std::enable_if<std::is_floating_point<T>::value>::type* = 0) {
std::cout << "foo called with floating-point type" << std::endl;
}
int main() {
foo(10); // Calls integral version
foo(10.5); // Calls floating-point version
// foo("hello"); // Error, neither integral nor floating-point
}
In this example:
foo function template uses std::enable_if in the function signature to control which function version is selected based on the type of T.T is an integral or floating-point type.Besides querying properties of types, C++ type traits can transform types at compile-time. Examples include:
std::remove_const<T>: Removes const qualifier from a type.std::add_pointer<T>: Adds a pointer to a type.std::remove_reference<T>: Removes reference from a type.std::decay<T>: Converts array or function types to pointers, and removes references and cv-qualifiers.std::remove_const#include <type_traits>
#include <iostream>
int main() {
typedef const int ConstInt;
typedef std::remove_const<ConstInt>::type NonConstInt;
std::cout << std::is_const<ConstInt>::value << std::endl; // true
std::cout << std::is_const<NonConstInt>::value << std::endl; // false
}
In this example:
std::remove_const removes the const qualifier from ConstInt, and NonConstInt is not a const type.Here’s a list of some common type traits from the C++ standard library:
std::is_arithmetic: Checks if a type is either integral or floating-point.std::is_class: Checks if a type is a class.std::is_enum: Checks if a type is an enumeration.std::is_trivially_copyable: Checks if a type can be trivially copied.std::remove_reference, std::add_reference: Removes/adds a reference to a type.std::remove_pointer, std::add_pointer: Removes/adds a pointer to a type.std::make_signed, std::make_unsigned: Converts a type to its signed/unsigned counterpart.std::is_base_of<Base, Derived>: Checks if one type is derived from another.std::is_convertible<From, To>: Checks if one type is convertible to another.Type traits provide powerful tools for querying and transforming types at compile time in C++. They are a critical part of template metaprogramming and allow programmers to make compile-time decisions based on type properties. Whether used for type checking, enabling/disabling template instantiations, or optimizing code, type traits allow for more robust, flexible, and efficient C++ code.