Default Template Arguments
Template argument deduction lets the compiler determine template arguments from the function arguments in a call, as with Source being deduced to double from 1.2.
Four Calls to the Same Template
Imagine a single conversion function template named to, and four different lines of C++ that all call it with the same argument, 1.2. The four lines differ in only one way: how much of the template argument list the programmer writes out. Some lines spell out every template argument, some spell out only one, and some leave the angle brackets out or empty. Each version produces the same kind of call, but the amount of work left to the compiler changes from line to line.
The question this article answers is simple to state: in each of those calls, how much of the work does the compiler do for you? To answer it, you need two mechanisms. The first is template argument deduction, where the compiler works out a template argument from the function arguments in the call. The second is default template arguments, where a value is supplied by the template itself when nothing else provides one. Keep the four calls in mind as you read, because by the end you will be able to say exactly where each template argument came from.
What do you think happens?
Given only the template header and the call to(1.2), what type do you expect the result to have?
Reveal answer
std::string
The argument 1.2 is a double, so Source is deduced to double. Nothing in the call supplies Target, so the default std::string is used, and the result is a std::string. The argument 1.2 controls Source, not the result type; the default controls Target.
Setting Up the Template
The examples in this article use one function template with two template parameters. The first parameter, Target, has a default argument of std::string. The second parameter, Source, has no default and is taken from the function argument. Parameters with defaults are listed first here, and the parameter without a default follows it; this is legal for function templates because Source can be deduced at the call site.
- Target has a default argument, so the call does not have to supply it
- Source has no default; it must come from deduction or be written explicitly
- The declaration is a sketch: the body that converts value into a Target is not shown here
How the Compiler Deduces Source
Template argument deduction lets the compiler determine template arguments from the function arguments in a call. In the call to(1.2), the only function argument is the literal 1.2, which has type double. The parameter Source is matched to that argument, so the compiler deduces Source to be double. The programmer never wrote double anywhere in the call; the compiler read it off the argument.
Template argument deduction: the process by which the compiler determines a template argument from the function arguments supplied in a call, without the programmer writing that argument explicitly.
Where Target Gets Its Value
Deduction does not supply Target in this call, because the argument 1.2 carries no information about the conversion target. The default template argument fills that gap. When a template parameter has a default and the call does not provide a value for it, the default is used. For this template, the default is std::string, so Target is std::string.
This is why the two parameters in the same call can have different origins. One is defaulted and one is deduced, and both are needed to produce a fully resolved specialization of the template. The compiler combines the two results into a single concrete version of to, which is the same thing the explicit first line of the code states outright.
Tracing the Path From Call to Specialization
Follow the call to(1.2) step by step. The steps below describe how the two sources combine. Notice that the argument feeds only Source, and the default feeds only Target.
Read the diagram from the top. The call is matched against the parameters, and the argument 1.2 yields Source equal to double. Separately, the call provides no explicit Target, so the default std::string is used. The two results meet in the specialization to<std::string, double>, which is exactly the explicit form of the first line of the code.
Four Calls Compared
The four lines below are a reconstruction of the calls described in the source. Each one calls to with the argument 1.2. Read them as a set of experiments that change only how much of the template argument list is written.
to<std::string, double>(1.2);
to<std::string>(1.2);
to(1.2);
to<>(1.2);- 1Line 1 fixes both parameters, so nothing is left for the compiler to decide.
- 2Line 2 writes the leading parameter Target. Explicit template arguments fill parameters from the left, so Source is left for deduction and becomes double.
- 3Line 3 writes nothing. Target takes its default, std::string, and Source is deduced to double from 1.2.
- 4Line 4 writes the angle brackets with nothing inside. This is an empty explicit list, and it resolves to the same specialization as line 3.
- Both template arguments are written; this is the most explicit and verbose form
- Only the first (Target) is written; Source is deduced to double from 1.2
- No explicit list; Target is defaulted to std::string and Source is deduced to double
- Empty angle brackets; the same outcome as the line above
to<std::string, double>(1.2);Both template arguments written: Target is std::string and Source is double
to(1.2);Nothing written: Target comes from its default and Source is deduced to double
Reading to(1.2) Piece by Piece
Reading a call is easier when you attribute each template argument to its source. In the call to(1.2), the angle brackets are absent, so no template argument is written at all. The name to refers to the template, and the parenthesized 1.2 is the only function argument. From that argument, Source is deduced to double. Target is not written and is not deduced from anything, so it comes from its default.
Mapping Each Parameter to Its Source
Every template parameter in a call has exactly one of three origins: it was written explicitly, it was deduced from the function arguments, or it came from a default. The table below applies this to all four calls. Checking each parameter in turn tells you which origin produced it.
| Call | Target came from | Source came from |
|---|---|---|
| to<std::string, double>(1.2) | written explicitly | written explicitly |
| to<std::string>(1.2) | written explicitly | deduced from 1.2 as double |
| to(1.2) | default std::string | deduced from 1.2 as double |
| to<>(1.2) | default std::string | deduced from 1.2 as double |
Origin of each template parameter in the four calls
The mapping gives you a debugging rule as well. For the to family in this article, Target is the parameter that comes from the default. If the result type is not what you expected, look at Target first: a mismatch there points to the default. If the mismatch is in Source, look at the function argument that was deduced, because that is where Source came from.
When an Argument Must Be Written Out
A template argument needs to be written explicitly only when it cannot be deduced or when it has no default. This rule is the practical test for every call. If a parameter can be deduced from the function arguments, leaving it out is safe. If it has a default and nothing else needs to change, leaving it out is also safe. Only a parameter that is neither deducible nor defaulted forces the programmer to write it.
Mistakes When Reading a Call
Common mistake Assuming that an empty angle bracket pair or a missing list means the template argument is missing
What it looks liketo(1.2) and to<>(1.2) both resolve Target to std::stringWhy it failsA missing or empty explicit list does not mean the value is unknown. The default or the deduction still supplies it.
Do this insteadCheck each parameter separately for a written value, a deduced value, or a default.
Common mistake Attributing every template argument to the function argument 1.2
What it looks likeThinking that 1.2 determines the result type of to(1.2)
Why it failsThe argument 1.2 feeds only Source. Target is not deduced from it; it comes from the default.
Do this insteadTrace Source to the argument and Target to the default.
Common mistake Writing the leading argument and assuming the trailing one is the default
What it looks likeReading to<std::string>(1.2) as Target and Source both set by the programmer
Why it failsOnly the leading parameter was written, so Source is still deduced from the argument.
Do this insteadCount the written arguments from the left; the rest are deduced or defaulted.
Practice Attributing Arguments
For the call to<>(1.2), state for each template parameter whether its value was written, deduced, or defaulted, and name the value it ends up with.
Hints
- Empty angle brackets write no template argument.
- Ask first whether Source can be deduced from the argument 1.2.
- Target has a default in the template header.
Predict where a mismatch would come from if the call to<std::string>(1.2) produced a result type you did not expect. Name the parameter, and say which line of the call you would change to test your guess.
Hints
- Only the leading template argument is written in this call.
- Source is still deduced from 1.2, so it is not the parameter the programmer chose.
Key Takeaways
- Template argument deduction lets the compiler determine template arguments from the function arguments in a call; in to(1.2), Source is deduced to double from 1.2.
- A template parameter with a default uses that default when the call does not supply a value; in this template, Target defaults to std::string.
- A template argument must be written explicitly only when it cannot be deduced or when it has no default; if every argument is defaulted, the angle brackets can be omitted, as in to(1.2).
- When a result is unexpected, check each template parameter in turn to see whether it was written, deduced, or defaulted, which locates the source of the divergence.