Concepts / Type Traits in the C++ Standard Library

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.

  • Programming

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.

python
Output (expected)
true
true
false
Notes on the code
  • 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?

  • true, true, false

  • true, false, true

  • false, false, true

Reveal answer
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.

Input: a typefor example intInput: a valuefor example x holding 5std::is_arithmetic<T>asks a property of the typex > 0compares the stored valueResult: booltrue or falseResult: booltrue or false
How a type predicate and a value check differ in their input, timing, and result.

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.

Key point

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.

maincallerIs_arithmetic<int>T is replaced by intstd::is_arithmetic<int>computes the property forintcall with type intpass type int, read ::valueconstant truereturn bool true
What happens step by step when Is_arithmetic<int>() is called: which function receives the type argument, what gets computed, and what bool value comes back to the caller.
Worked example

Tracing the call Is_arithmetic<double>()

Follow the call Is_arithmetic<double>() from the call site to the printed output.

  1. 1
    Substitute the type

    The compiler replaces T with double everywhere in the function, so the body becomes return std::is_arithmetic<double>::value;.

  2. 2
    Evaluate 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.

  3. 3
    Return the bool

    The function returns that bool to the caller. No value of any variable was consulted at any point.

  4. 4
    Print the result

    main sends the returned bool to cout. With std::boolalpha set, the line printed is true.

Result

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.

python
Output (expected)
numeric type
non-numeric type
Notes on the code
  • 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.

inttruedoubletruebooltrueconst inttrueint&falsechar*falseenumfalse
Which types map to true or false under std::is_arithmetic, and where the result differs from what a learner might expect.

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 like
    Writing if (Is_arithmetic(x)) and passing a variable
    Why it fails

    Is_arithmetic is a template that takes a type, not a value, so the variable itself is not the input.

    Do this instead

    Pass 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 like
    Expecting Is_arithmetic<int&>() to return true
    Why it fails

    The predicate returns false for int&, because the type is a reference rather than an arithmetic type itself.

    Do this instead

    Use 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 like

    Expecting Is_arithmetic<char*>() to return true because the pointed-to type is a character

    Why it fails

    The predicate is applied to char*, which is a pointer type, and that type is not arithmetic.

    Do this instead

    Test 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

MEDIUM

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_arithmetic for that type.

Key Takeaways on Type Predicates

  1. 1
    A type predicate checks a property of a type, not a value, and the type is supplied as a template argument.
  2. 2
    Is_arithmetic<T>() returns std::is_arithmetic<T>::value, a bool that is true for integer and floating-point types.
  3. 3
    Because the result is a bool, it can drive an if statement and select a branch based on the type alone.
  4. 4
    Some 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.

Related concepts

More concepts starting with T