– Written by Triangles on February , • updated on February , • ID – –
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 transforms 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
header:
template
struct is_floating_point;This type trait tells whether a type
T
is floating point or not. The member constant - calledvalue
for type traits that ask a question - will be either set totrue
orfalse
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 typeT
it takes in input:) https : //en.wikipedia.org/wiki/Type_introspection (https://gracicot.github.io/reflection/) (/ / 10 / reflection-present.htmltemplate
struct remove_reference;This type trait basically turns
T &
intoT
. The member constant - calledtype
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:
# include
if constexpr syntax: an#include class Class {}; int main () { std :: cout :: value :: value :: value This program will output:
0 1 0
How does it work exactly?
In the snippet above you are passing three different types to the template struct
std :: is_floating_point
: a customClass
type, afloat
and anint
. The compiler, as with any regular template stuff, will generate three different structs for you under the hood:struct is_floating_point_Class { static const bool value=false; }; struct is_floating_point_float { static const bool value=true; }; struct is_floating_point_int { static const bool value=false; };
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 anunsigned 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 )
if
statement that works at compile time;the C 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
andstd :: is_unsigned
.The code looks like this:
void algorithm_signed (int i) {/*...*/} void algorithm_unsigned (unsigned u) {/*...*/} template
void algorithm (T t) { if constexpr (std :: is_signed :: value) algorithm_signed (t); else if constexpr (std :: is_unsigned :: value) algorithm_unsigned (t); else static_assert (std :: is_signed
:: value || std :: is_unsigned:: value, "Must be signed or unsigned!"); } ) (https://en.cppreference.com/w/cpp/language/static_assert) https://www.ibm.com/developerworks/community/blogs/ (f-be) - 4bc0 - (c5 -) f3 / entry / introduction_to_type_traits_in_the_c_standard_library? lang=en (https://en.wikipedia.org/wiki/Reflection_) computer_programmingIn words, the template function
algorithm
acts as a dispatcher : when instantiated, the compiler will grab the right function according to the typeT
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 does not meet the criteria.Some usage examples:
(https://en.cppreference.com/w/cpp/typesalgorithm (3); // T is int, include algorithm_signed () unsigned x=3; algorithm (x); // T is unsigned int, include algorithm_unsigned () algorithm ("hello"); // T is string, build error!
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 thestd :: remove_reference
type trait to shave off the
&
(if any) from the type in input and to return a cleanT
with the
&&
attached. A possible implementation:template
typename remove_reference :: type && move (T && arg) { return static_cast(arg); } 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 and greater introduced a simplified syntax thanks to some helper aliases that end with_ v
and_ t
respectively. So for example:std :: is_signed :: value; / ---> / std :: is_signed_v ; std :: remove_const :: type; / ---> / std :: remove_const_t ;
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 and other techniques from the dark corners of C .How does a type trait know about a type? How can it infer that, for example,
std :: is_signed_v
is true for anint
? Most basic type traits are the result of template metaprogramming tricks, SFINAE , tag dispatchSome 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 62: 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 ...
Sources
https: / /en.cppreference.com/w/cpp/language/type
https://en.wikipedia.org/wiki/Intrinsic_function
(https://stackoverflow.com/questions/) / which-type-traits- cannot-be-implemented-without-compiler-hooks
(https://visualstudiomagazine.com/articles/) / / / c-introsp ection.aspxRead More
GIPHY App Key not set. Please check settings