# Type Traits
The type_traits header contains a set of template classes and helpers to transform and check properties of types at compile-time.
These traits are typically used in templates to check for user errors, support generic programming, and allow for optimizations.
Most type traits are used to check if a type fulfils some criteria. These have the following form:
If the template class is instantiated with a type which fulfils some criteria foo , then is_foo<T> inherits from std::integral_constant<bool,true> (a.k.a. std::true_type ), otherwise it inherits from std::integral_constant<bool,false> (a.k.a. std::false_type ). This gives the trait the following members:
# Constants
static constexpr bool value
true if T fulfils the criteria foo , false otherwise
# Functions
# Types
| Name | Definition |
|---|---|
| value_type | bool |
| type | std::integral_constant<bool,value> |
The trait can then be used in constructs such as static_assert
(opens new window) . An example with std::is_pointer :
There are also various traits which transform types, such as std::add_pointer and std::underlying_type . These traits generally expose a single type member type which contains the transformed type. For example, std::add_pointer<int>::type is int* .
# Type Properties
Type properties compare the modifiers that can be placed upon different variables. The usefulness of these type traits is not always obvious.
Note: The example below would only offer an improvement on a non-optimizing compiler. It is a simple a proof of concept, rather than complex example.
e.g. Fast divide by four.
Is Constant:
This will evaluate as true when type is constant.
Is Volatile:
This will evaluate as true when the type is volatile.
Is signed:
This will evaluate as true for all signed types.
Is Unsigned:
Will evaluate as true for all unsigned types.
# Type relations with std::is_same<T, T>
The std::is_same<T, T> type relation is used to compare two types. It will evaluate as boolean, true if the types are the same and false if otherwise.
The std::is_same type relation will also work regardless of typedefs. This is actually demonstrated in the first example when comparing int == int32_t however this is not entirely clear.
Using std::is_same to warn when improperly using a templated class or function.
When combined with a static assert the std::is_same template can be valuable tool in enforcing proper usage of templated classes and functions.
e.g. A function that only allows input from an int and a choice of two structs.
# Fundamental type traits
There are a number of different type traits that compare more general types.
Is Integral:
Evaluates as true for all integer types int , char , long , unsigned int etc.
Is Floating Point:
Evaluates as true for all floating point types. float , double , long double etc.
Is Enum:
Evaluates as true for all enumerated types, including enum class .
Is Pointer:
Evaluates as true for all pointers.
Is Class:
Evaluates as true for all classes and struct, with the exception of enum class .
# Remarks
Type traits are templated constructs used to compare and test the properties of different types at compile time. They can be used to provide conditional logic at compile time that can limit or extend the functionality of your code in a specific manner. The type traits library was brought in with the c++11 standard which provides a number different functionalities. It is also possible to create your own type trait comparison templates.
How to use type traits?
As a spin-off of the concepts series, I delved into the world of type traits and last week we started to discuss what type traits are and how they are implemented.
As I prefer to keep my articles somewhere between 5 and 10 minutes of reading time, I decided to stop right there. With the basic understanding of type traits, now it’s time to see how to use them. We are going to see how they can set conditions for compiling different template specializations and then how they can alter types.
Conditional compilation
As we’ve already mentioned we can use type traits to disallow the usage of templates with certain types based on their characteristics. Just to emphasize, this has no runtime costs, all the checks (and errors) happen at compile-time.
Let’s see a basic example.
Let’s say that we want to write a function called addSigned(T a, T b) where we only add unsigned number thus we are sure that the result is bigger than any of the inputs (we ignore overflow errors).
If we write a simple template, the problem is we can still call it with unsigned numbers.
Type traits can help us solve this issue in different ways.
static_assert
We can simply statically assert that T is an unsigned type.
It’s worth reminding ourselves, that when used in a boolean context, we can’t simply use std::is_unsigned<T> as it’s already a type that is not boolean — it inherits from std::integral_constant — but we need its value static member constant that is a bool . Since C++17 we can use std::is_unsigned_v<T> directly.
So static_assert takes the compile-time boolean as a first parameter and an error message as the second parameter.
Then if we use it with some other types, we’ll get the — hopefully — nice error message from the compiler.
If you think that the error message is not good enough, just write a better one as it’s taken from your static_assert .
std::enable_if
Now let’s say that we want to support different additions and we want to use the same function signature T add(T a, T b) . We can use the std::enable_if metafunction from the <type_traits> header.
We can see that we were able to define two functions with the same signature, while only the template parameter list is different. There we used enable_if to express that one or the other function should be called in case the is_signed or is_unsigned trait is evaluated to true.
In case, std::enable_if receives true as its first argument, then it will have an internal type that is taken from the second argument. If its first argument evaluates to false , then it doesn’t have an internal type and the substitution fails. In order not to end up with a compilation error we default these types to nullptr .
I know this is still a bit vague, but this part that is often referred to as SFINAE deserves its own article. Something we are going to cover in detail in the coming weeks.
if constexpr
Since C++17, there is a third way, as we have if constexpr at our hands. With if constepxr we can evaluate conditions at compile time and we can discard branches from the compilation. With if constexpr you can significantly simplify obscure metaprogramming constructs.
Let’s see how we can use it to use it to cut down our previous example:
With if constexpr we can evaluate conditions at compile-time and as such we can make compile-time decisions based on the type traits. I’m sure I’m not alone considering it much simpler to read than enable_if
Could we make it simpler? Yes and that’s true for all the previous examples. Since C++17 there is a shortcut I already referred to, you don’t have to access value in a type_trait, there are metafunctions to return the value directly. They are called the same way as the corresponding type traits, but appended with _v :
Altering types
Now let’s have a look at how type traits can alter types. There are templates shipped in the <type_traits> header that can
- add or remove const and/or volatile specifiers from a given type
- add or remove reference or pointer from a given type
- make a type signed or unsigned
- remove dimensions from an array
- etc. (including enable_if, that we already saw briefly)
Let’s see three examples.
Adding/removing the const specifier
With std::add_const / std::remove_const you can add/remove the topmost const of a type:
When you make comparisons, make sure that you access the type nested member. Since C++17 you can directly get the type by using std::add_const_t instead of std::add_const<T>::type to keep things shorter and more readable.
But how can this be useful? The above example already sparks an answer. If you want to compare two types regardless of their qualifiers, first you can remove the const qualifiers and make the comparison with std::is_same only after. Without calling std::remove_const , you might compare T with const T which are different, but after calling it, you’d compare T with T .
Following the same logic, you can find a use case for removing references or pointers as well.
Turning an unsigned number into a signed one
You can use type traits to turn a signed type into an unsigned one or the other way around.
As you can see, we used the _t -style helper functions to get back directly the modified type.
std::conditional to choose between two types at compile time
With std::conditional you can choose between two types based on a compile time condition. You can imagine it as the compile-time ternary operator though probably it’s a bit more difficult to read.
You can find examples where based the condition is the size of the passed in type. There might be cases, where you want to choose a type based on that for example to have better padding, to fit more the memory layout. How to make a decision based on the size? It’s very simple, just use the sizeof operator:
Conclusion
Today we had a look into how to use type traits for conditional compilation and how to use them to alter types. We mentioned SFINAE as well, which will be the topic in a couple of weeks.
A quick primer on type traits in modern C++
Discovering one of the pillars of C++ generic programming.
Type traits are a clever technique used in C++ template metaprogramming that gives you the ability to inspect and transform the properties of types.
For example, given a generic type T — it could be int , bool , std::vector or whatever you want — with type traits you can ask the compiler some questions: is it an integer? Is it a function? Is it a pointer? Or maybe a class? Does it have a destructor? Can you copy it? Will it throw exceptions? . and so on. This is extremely useful in conditional compilation, where you instruct the compiler to pick the right path according to the type in input. We will see an example shortly.
Type traits can also apply some transformation to a type. For example, given T , you can add/remove the const specifier, the reference or the pointer, or yet turn it into a signed/unsigned type and many other crazy operations. Extremely handy when writing libraries that make use of templates.
The beauty of these techniques is that everything takes place at compile time with no runtime penalties: it’s template metaprogramming, after all. I assume you know a bit about C++ templates for the rest of this article. This guide is a great introduction if you don’t.
What is a type trait?
A type trait is a simple template struct that contains a member constant, which in turn holds the answer to the question the type trait asks or the transformation it performs. For example, let’s take a look at std::is_floating_point , one of the many type traits defined by the C++ Standard Library in the <type_traits> header:
This type trait tells whether a type T is floating point or not. The member constant — called value for type traits that ask a question — will be either set to true or false according to the type passed in as template argument.
On the other hand, for example std::remove_reference is a type trait that alters the type T it takes in input:
This type trait basically turns T& into T . The member constant — called type for those type traits that modify a type — contains the result of the transformation.
How do I use a type trait?
Simply instantiate the template struct with the type you want, then inspect its member constant and act accordingly. For example, let’s say you just want to print out if a type is floating point or not:
This program will output:
How does it work exactly?
In the snippet above you are passing three different types to the template struct std::is_floating_point : a custom Class type, a float and an int . The compiler, as with any regular template stuff, will generate three different structs for you under the hood:
At this point it’s just a matter of reading the value member inside those structs created by the compiler. Being static, you have to access the member constant with the :: syntax. Just keep in mind that this is template metaprogramming, so everything takes place at compile time.
Type traits in action, part 1: conditional compilation
Now that we have grasped the idea behind type traits, let’s try to use them in some real world scenarios. Suppose you have two functions for the same algorithm: one that works with signed integers and another one that is super optimized for unsigned ones. You want the compiler to pick the signed one when an int is passed in and the unsigned one in case of an unsigned int to take advantage of the optimizations. This is the conditional compilation I mentioned before.
For this task I will be using three tools:
- the C++17 if constexpr syntax: an if statement that works at compile time;
- the C++11 static_assert function that, as the name implies, triggers an assert at compile time if the condition is not met;
- two self-explanatory type traits: std::is_signed and std::is_unsigned .
The code looks like this:
In words, the template function algorithm acts as a dispatcher: when instantiated, the compiler will grab the right function according to the type T passed in. If signed, algorithm_signed will be included; if unsigned, algorithm_unsigned will be included instead. Finally, throw a static assertion (i.e. a build error) if the type doesn’t meet the criteria.
Some usage examples:
Type traits in action, part 2: altering types
Type traits are also used to apply transformation to types. A typical usage of this magic comes from the C++ Standard Library and std::move : the utility function that turns a type T into an rvalue reference T&& . This is an important operation that paves the way for move semantics.
Internally, std::move makes use of the std::remove_reference type trait to shave off the & (if any) from the type in input and to return a clean T with the && attached. A possible implementation:
Transformations like this one are widespread across the whole Standard Library, often used to optimize how function parameters flow across nested template function calls. All in all some of these type traits are rarely useful for average C++ projects, unless you are writing a library or performing some clever metaprogramming tricks.
Beautifying type traits
Reading ::value and ::type everywhere in your code is confusing. Luckily, C++14 and greater introduced a simplified syntax thanks to some helper aliases that end with _v and _t respectively. So for example:
These helpers exist for all type traits that query a type or apply a transformation on it.
More type traits trivia and further readings
Type traits act as a foundation for many C++ features and, as always, in this article I’ve barely scratched their surface. The following is a list of additional topics that deserve more love in the future.
The source of type traits knowledge
How does a type trait know about a type? How can it infer that, for example, std::is_signed_v<T> is true for an int ? Most basic type traits are the result of template metaprogramming tricks, SFINAE, tag dispatch and other techniques from the dark corners of C++.
Some type traits need additional help instead. For example the std::is_abstract type trait — which tells if a type is an abstract class or not — can’t be generated with template metaprogramming alone. For this reason developers who work on the Standard Library make use of intrinsics: special built-in functions provided by the compiler that give more insight about the type in question, thanks to the deep knowledge a compiler has on the program it takes in input. More information here and here.
Type traits and concepts
Concepts are an important addition in C++20: an elegant and expressive way to put a constraint on the types a template function or class can take in. For example, in the conditional compilation example above I could have used a concept instead of triggering the static assertion at the end. Not surprisingly, concepts are based on the numerous type traits defined in the Standard Library. More information about concepts here.
Type traits provide introspection
Introspection is the ability of a program to examine the type or properties of an object. For example, with introspection you can ask an object if it has a specific member function in order to call it.
C++ is not capable of introspection at runtime, but as we saw in this article it does a good job at compile time thanks to type traits. We definitely used compile-time introspection when we checked if T was signed or not in the previous examples.
On the other hand, reflection refers to the ability of a program to observe and alter its own structure or its behavior. There’s no such thing in C++ for now, but some programming artists are working on crazy libraries such as magic_get by leveraging the power of type traits combined to template metaprogramming. There are also some proposals to include reflection in modern C++, drafted here and here. Time will tell.
Type traits c что это
The C++ front end implements syntactic extensions that allow compile-time determination of various characteristics of a type (or of a pair of types).
If type is const -qualified or is a reference type then the trait is false . Otherwise if __has_trivial_assign (type) is true then the trait is true , else if type is a cv-qualified class or union type with copy assignment operators that are known not to throw an exception then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If __has_trivial_copy (type) is true then the trait is true , else if type is a cv-qualified class or union type with copy constructors that are known not to throw an exception then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If __has_trivial_constructor (type) is true then the trait is true , else if type is a cv class or union type (or array thereof) with a default constructor that is known not to throw an exception then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If type is const — qualified or is a reference type then the trait is false . Otherwise if __is_trivial (type) is true then the trait is true , else if type is a cv-qualified class or union type with a trivial copy assignment ([class.copy]) then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If __is_trivial (type) is true or type is a reference type then the trait is true , else if type is a cv class or union type with a trivial copy constructor ([class.copy]) then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If __is_trivial (type) is true then the trait is true , else if type is a cv-qualified class or union type (or array thereof) with a trivial default constructor ([class.ctor]) then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If __is_trivial (type) is true or type is a reference type then the trait is true , else if type is a cv class or union type (or array thereof) with a trivial destructor ([class.dtor]) then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If type is a class type with a virtual destructor ([class.dtor]) then the trait is true , else it is false . Requires: If type is a non-union class type, it shall be a complete type.
If type is an abstract class ([class.abstract]) then the trait is true , else it is false . Requires: If type is a non-union class type, it shall be a complete type.
If type is an aggregate type ([dcl.init.aggr]) the trait is true , else it is false . Requires: If type is a class type, it shall be a complete type.
__is_base_of (base_type, derived_type)
If base_type is a base class of derived_type ([class.derived]) then the trait is true , otherwise it is false . Top-level cv-qualifications of base_type and derived_type are ignored. For the purposes of this trait, a class type is considered is own base. Requires: if __is_class (base_type) and __is_class (derived_type) are true and base_type and derived_type are not the same type (disregarding cv-qualifiers), derived_type shall be a complete type. A diagnostic is produced if this requirement is not met.
If type is a cv-qualified class type, and not a union type ([basic.compound]) the trait is true , else it is false .
If __is_class (type) is false then the trait is false . Otherwise type is considered empty if and only if: type has no non-static data members, or all non-static data members, if any, are bit-fields of length 0, and type has no virtual members, and type has no virtual base classes, and type has no base classes base_type for which __is_empty (base_type) is false . Requires: If type is a non-union class type, it shall be a complete type.
If type is a cv enumeration type ([basic.compound]) the trait is true , else it is false .
If type is a class or union type marked final , then the trait is true , else it is false . Requires: If type is a class type, it shall be a complete type.
If type is a literal type ([basic.types]) the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If type is a cv POD type ([basic.types]) then the trait is true , else it is false . Requires: type shall be a complete type, (possibly cv-qualified) void , or an array of unknown bound.
If type is a polymorphic class ([class.virtual]) then the trait is true , else it is false . Requires: If type is a non-union class type, it shall be a complete type.
If type is a standard-layout type ([basic.types]) the trait is true , else it is false . Requires: type shall be a complete type, an array of complete types, or (possibly cv-qualified) void .
If type is a trivial type ([basic.types]) the trait is true , else it is false . Requires: type shall be a complete type, an array of complete types, or (possibly cv-qualified) void .
If type is a cv union type ([basic.compound]) the trait is true , else it is false .
The underlying type of type . Requires: type shall be an enumeration type ([dcl.enum]).
When used as the pattern of a pack expansion within a template definition, expands to a template argument pack containing integers from 0 to length-1 . This is provided for efficient implementation of std::make_integer_sequence .