Templates and Type Deduction
Metaprogramming is the use of C++ features for compile-time computation, and template metaprogramming is the form that involves templates.
A Question the Compiler Must Answer
Imagine you are writing a function that only makes sense for whole numbers. You want the program to refuse to compile if someone passes it a type such as double, instead of failing in some confusing way later. The difficulty is that ordinary C++ code runs only after the program has already been built. Ordinary code therefore cannot answer the question 'is this a whole-number type?' on your behalf. What you need is a way to ask questions about types while the compiler is still working.
What do you think happens?
A function is written to accept only whole-number types. A caller passes it a double. When should the rejection happen?
Reveal answer
While the compiler is working, before the program exists.
The source frames the goal as refusing to compile bad code. A question the compiler can answer during translation can reject the code early, before any program runs.
Compile Time Versus Run Time
C++ includes mechanisms for compile-time computation. These are features that let part of the work of a program happen when the compiler translates the source code, not when the resulting program runs. The benefit described for these mechanisms is tighter type checking and better performance than would otherwise have been possible.
It helps to read the process as a sequence of stages. The compiler reads the code, evaluates the compile-time questions the code asks, and only then produces a program. By the time the program starts running, the answers to those questions are already fixed. The running program simply uses them.
The stages above are the core of the idea. Everything left of the program-running step happens while the compiler works. Everything at and after that step is ordinary execution, and it can only rely on what was decided earlier.
Why It Is Called Metaprogramming
Metaprogramming is the use of C++ features for compile-time computation. Template metaprogramming is the form of metaprogramming that involves templates.
The word meta points at the idea that the program is computing about other code, specifically about types, instead of only computing ordinary values. Ordinary programming manipulates data the program will use later. Metaprogramming manipulates information about types while the compiler works, so the eventual program is shaped by those answers.
Type Functions as Questions About Types
The source introduces type functions as one of the mechanisms behind compile-time computation. Think of a type function as a question. It takes a type as input, checks something about that type, and gives back an answer. The answer is determined while the compiler runs, so the rest of the code can treat it as a fixed fact.
The questions can be simple, such as whether a type is an integer type. They can also be about related types, such as what kind of element a container's iterator refers to. Because the compiler can answer these questions before the program exists, a bad choice of type can be rejected early. This is the source of the tighter type checking the source describes.
Tracing a Type Question in Code
The following illustrative example applies the whole-number scenario from the opening. It uses the standard library predicate std::is_integral, which asks whether a type is an integer type and exposes the answer through its value member. The static_assert line asks that question at compile time and stops compilation if the answer is false.
#include <type_traits>
template <typename T>
void only_whole_numbers(T value) {
static_assert(std::is_integral<T>::value, "T must be an integer type");
// body that uses value goes here
}
int main() {
only_whole_numbers(42); // T is int: the question answers true, so this compiles
only_whole_numbers(2.5); // T is double: the question answers false, so compilation stops
}- 1The template parameter T stands for whatever type the caller passes in.
- 2When the compiler sees
only_whole_numbers(42), T becomes int, and std::is_integral<int>::value is true, sostatic_assertpasses. - 3When the compiler sees
only_whole_numbers(2.5), T becomes double, and std::is_integral<double>::value is false, sostatic_assertreports the message and the program is not built. - 4No part of this check waits for the program to run. The answer is already known when the compiler finishes.
- std::
is_integral<T> is a type predicate: a type-level question with a true or false answer static_assertevaluates the answer while the compiler runs- The call with 2.5 fails to compile rather than failing later at run time
Reading the Answer to a Type Question
Given a type question that asks whether a type is an integer type, what does the compiler conclude for int and for double?
- 1Identify the input
The type passed to the question is the input. For the first case it is int, and for the second it is double.
- 2Run the check
The predicate asks whether the input is an integer type. int is an integer type, and double is not.
- 3Read the produced answer
For int the answer is true. For double the answer is false.
- 4Act on the fixed fact
A false answer can be used to reject the code during compilation, before the program is ever run.
The question yields true for int and false for double, and that answer is fixed before the program runs.
Standard Library Tools for Reading Types
The source presents two facilities that the standard library provides as concrete examples of compile-time questions. They are worth keeping separate in your mind. One reports facts about a type, and the other tests a yes or no question about it.
Choosing Between a Predicate and iterator_traits
| Tool | Question it asks | Form of the answer |
|---|---|---|
| std::is_integral<T> | Is T an integer type? | true or false |
| std::is_same<T,U> | Are T and U the same type? | true or false |
| std::iterator_traits<Iter> | What facts does this iterator type report? | member typedefs such as value_type and iterator_category |
Type predicates test a property, while iterator_traits reports properties
When you need a decision, such as whether to allow a type, reach for a predicate. When you need to know a detail about an iterator, such as its element type, reach for iterator_traits. Both are compile-time questions, so both are answered before the program runs.
Mistakes Beginners Make With Compile-Time Questions
Common mistake Expecting a type predicate to be checked while the program runs
What it looks likeWriting a normal if statement on std::is_integral<T>::value and assuming it protects the program at run time.
Why it failsThe predicate's answer is produced while the compiler works. Ordinary code runs after the program is built, so it cannot be the place where the type question is first answered.
Do this insteadUse the answer as a compile-time guard, such as a static_assert, when the goal is to reject bad types before the program exists.
Common mistake Treating iterator_traits as a yes or no test
What it looks likeExpecting iterator_traits<Iter> to return true or false for an iterator.
Why it failsiterator_traitsreports information about an iterator type, through member typedefs such asvalue_type. It does not test a property with a boolean answer.Do this insteadRead the member typedef you need, such as value_type, and use a predicate only when you need a true or false decision.
Common mistake Assuming the compiler asks these questions at run time, which hides the reason the technique exists
Common mistake Confusing a type with a value that the program computes
Why it failsMetaprogramming manipulates information about types during compilation. Its answers are fixed before execution starts, so they are not values the program computes later.
Do this insteadAsk whether the question is about a type while the compiler works. If it is, its answer belongs to compile time.
Practice: Classify Each Question
For each question below, decide whether the right tool is a type predicate or iterator_traits, and whether the answer is true or false or a set of member typedefs. Questions: (1) Should the compiler reject a type that is not an integer type? (2) What element type does an iterator refer to? (3) Are two given types exactly the same type?
Hints
- A question that needs a yes or no answer points toward a type predicate.
- A question that asks what an iterator refers to or what kind of iterator it is points toward
iterator_traits. - std::
is_same<T,U> is the predicate that compares two types.
Key Takeaways
- Metaprogramming is the use of C++ features for compile-time computation, and template metaprogramming is the form that involves templates.
- Compile-time computation lets part of a program's work happen while the compiler translates the source, which supports tighter type checking and better performance.
- A type function takes a type, asks a question about it, and produces an answer that is fixed before the program runs.
- Type predicates such as std::
is_integral<T> and std::is_same<T,U> give true or false answers, whileiterator_traitsreports information about an iterator type.