Type Traits in the C++ Standard Library
A template function that returns a type predicate has two jobs: it receives a type as a template argument, and it returns a bool. Everything between those two events is delegated to the trait.
Predicting Three Printed Lines
Most of the code you write asks questions about values. Is this number greater than zero? Is this string empty? A type predicate asks a different kind of question: it checks a property of a type itself. The idea is easiest to learn by running code first and then explaining what happened. Read the small program below, then predict each printed line before you open the reveal.
true
true
false- The template line makes T a placeholder for a type supplied at the call site.
- The body returns the constant value std::
is_arithmetic<T>::value, a bool. - std::boolalpha makes cout print the words true and false instead of 1 and 0.
What do you think happens?
What three lines does the program print, in order?
Reveal answer
true, true, false
int and double are both arithmetic types, so their calls return true. char* is a pointer type, which is not arithmetic, so the third call returns false. Note the language label on the code block above is set to python only in the metadata; the code itself is C++.
Values Versus Types
The variable or value you hold in a program is one thing. The type that describes that value is another. A type predicate does not look at a number at all. It asks the type a yes-or-no question, and the answer is available as a constant value that the rest of your code can use. The only input that matters to std::is_arithmetic is the type itself.
Both checks end in a bool, which is why they can drive the same kind of if statement. What differs is the question. The value check changes as the program runs and the variable changes. The type predicate answers the same question about int every time, because the type int does not change.
Reading the Template Function
The line template <typename T> declares a function template. T is a placeholder that will be replaced by a real type each time the function is used. A template is needed because the type is not fixed inside the function. The same Is_arithmetic function can be asked about int, double, or any other type, and each call uses its own type argument.
The return type of Is_arithmetic is bool, so the function produces either true or false. Its entire body is a single return statement that reads std::is_arithmetic<T>::value. Nothing in the function examines a variable or a value. The only input that matters is T. The standard library trait std::is_arithmetic does the actual work, and Is_arithmetic simply passes the type along and forwards the answer.
A template function that returns a type predicate has two jobs: it receives a type as a template argument, and it returns a bool. Everything between those two events is delegated to the trait.
Tracing the Three Calls
Trace the three calls from the first program as a sequence. Each call substitutes one type for T, evaluates std::is_arithmetic for that type, and returns the resulting bool to main. The printed lines are the three returned values, in the order the calls appear.
Tracing the call Is_arithmetic<double>()
Follow the call Is_arithmetic<double>() from the call site to the printed output.
- 1Substitute the type
The compiler replaces T with double everywhere in the function, so the body becomes return std::
is_arithmetic<double>::value;. - 2Evaluate the trait
std::
is_arithmetic<double> asks whether double is an arithmetic type. The source describes arithmetic types as including floating-point types, so the value is true. - 3Return the bool
The function returns that bool to the caller. No value of any variable was consulted at any point.
- 4Print the result
main sends the returned bool to cout. With std::boolalpha set, the line printed is true.
The second printed line is true.
Using the Result in a Conditional
The main reason to write a type predicate is to let a program choose a path based on a type. Once Is_arithmetic<T>() returns a bool, it can be used anywhere a bool is expected, including an if statement. The predicate call is the only place where the type enters the decision. After it returns, the program branches only on the bool, which means the same branch logic can be reused for many different types without rewriting it.
numeric type
non-numeric type- The if condition is the bool produced by the type predicate.
- Describe<int>() takes the true branch because int is arithmetic.
- Describe<char*>() takes the else branch because char* is not arithmetic.
Where Results Differ From Expectations
The predicate answers a question about the exact type it receives, and that answer does not always match an intuitive guess about a type. The map below shows several types and the result std::is_arithmetic gives for each. Read it as a lookup table from type to bool.
Three entries deserve attention. int& is a reference to an int, and the predicate returns false for it, so a function that accepts references to numbers will not take the numeric branch unless the reference is removed first. const int is true, so the qualifier does not hide the underlying arithmetic type. bool is true, even though it is often thought of as a logical rather than numeric type. An enum is false, even when its underlying values are integers. The rule to take away is that the predicate asks about the type as written in the template argument, so the exact form of that type decides the answer.
Mistakes Beginners Make with Type Predicates
Common mistake Checking the value of a variable instead of the type
What it looks likeWriting if (Is_arithmetic(x)) and passing a variableWhy it failsIs_arithmetic is a template that takes a type, not a value, so the variable itself is not the input.
Do this insteadPass the type as a template argument, for example Is_arithmetic<int>(), and let the variable's declared type determine it.
Common mistake Assuming a reference to a number counts as arithmetic
What it looks likeExpecting Is_arithmetic<int&>() to return trueWhy it failsThe predicate returns false for int&, because the type is a reference rather than an arithmetic type itself.
Do this insteadUse the plain type, such as int, when the question is whether the underlying type is arithmetic.
Common mistake Expecting a pointer to behave like the number it points to
What it looks likeExpecting Is_arithmetic<char*>() to return true because the pointed-to type is a character
Why it failsThe predicate is applied to char*, which is a pointer type, and that type is not arithmetic.
Do this insteadTest the pointer type directly and expect false, then write separate logic for pointer types if needed.
Common mistake Forgetting that the if statement only sees a bool and cannot tell which type produced it
Practice: Tracing an Unexpected Branch
Using the Describe function from the conditional example, predict the output of calling Describe<bool>() and Describe<int&>() in that order. For each call, first name the type that the predicate receives, then decide which branch is taken, and only then state the printed line.
Hints
- bool is listed in the table as true, so think about which branch that selects.
- int& is a reference type. Ask whether the predicate treats it as the arithmetic type int.
- Trace each call by writing the type substitution first, then the value of std::
is_arithmeticfor that type.
Key Takeaways on Type Predicates
- 1A type predicate checks a property of a type, not a value, and the type is supplied as a template argument.
- 2Is_arithmetic<T>() returns std::
is_arithmetic<T>::value, a bool that is true for integer and floating-point types. - 3Because the result is a bool, it can drive an if statement and select a branch based on the type alone.
- 4Some results differ from intuition, such as int& returning false, so when a result is unexpected, trace the type argument at the failing call first.
At a glance
- A type predicate checks a property of a type, not a value, and the type is supplied as a template argument.
- Is_arithmetic<T>() returns std::
is_arithmetic<T>::value, a bool that is true for integer and floating-point types. - Because the result is a bool, it can drive an if statement and select a branch based on the type alone.
- When a predicate result is unexpected, trace the type argument at the failing call first.