Concepts / Default Template Arguments

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.

  • Programming

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?

  • double

  • std::string

  • It cannot be determined

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

cpp
Notes on the code
  • 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.

Definition

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.

to(1.2)the call as writtenDeduce Sourcefrom the argument 1.2Source = doublededucedto<std::string,double>fully resolvedspecializationTarget not writtenno explicit value in thecallUse defaultstd::stringmatch parametersargument typecheck bracketsfall back to defaultSourceTarget
What steps does the compiler take to go from the call to(1.2) to a concrete instantiation, and how does the argument type flow into Source?

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.

Code walkthrough
to<std::string, double>(1.2);
to<std::string>(1.2);
to(1.2);
to<>(1.2);
  1. 1
    Line 1 fixes both parameters, so nothing is left for the compiler to decide.
  2. 2
    Line 2 writes the leading parameter Target. Explicit template arguments fill parameters from the left, so Source is left for deduction and becomes double.
  3. 3
    Line 3 writes nothing. Target takes its default, std::string, and Source is deduced to double from 1.2.
  4. 4
    Line 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
Before
to<std::string, double>(1.2);

Both template arguments written: Target is std::string and Source is double

After
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.

tofunction template nameTargetfrom the default:std::stringno angle bracketsno explicit templateargumentsSourcededuced from 1.2: double(1.2)the function argument
In the call to(1.2), which template parameter got its value from the default, and which got its value from the argument 1.2?

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.

CallTarget came fromSource came from
to<std::string, double>(1.2)written explicitlywritten explicitly
to<std::string>(1.2)written explicitlydeduced from 1.2 as double
to(1.2)default std::stringdeduced from 1.2 as double
to<>(1.2)default std::stringdeduced from 1.2 as double

Origin of each template parameter in the four calls

Targetstd::stringSourcedouble
For each template parameter (Target, Source), where does its final value come from when the call to(1.2) is made?

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.

Can it be deduced?from the function argumentsDoes it have adefault?in the template headerLeave it outsafe to omitWrite it explicitlyrequired at the call siteyesnoyesno
Given a template parameter that has a default or can be deduced, when does the call site still need to specify it explicitly, and what happens if a parameter that cannot be deduced is omitted?

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 like
    to(1.2) and to<>(1.2) both resolve Target to std::string
    Why it fails

    A missing or empty explicit list does not mean the value is unknown. The default or the deduction still supplies it.

    Do this instead

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

    Thinking that 1.2 determines the result type of to(1.2)

    Why it fails

    The argument 1.2 feeds only Source. Target is not deduced from it; it comes from the default.

    Do this instead

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

    Reading to<std::string>(1.2) as Target and Source both set by the programmer

    Why it fails

    Only the leading parameter was written, so Source is still deduced from the argument.

    Do this instead

    Count the written arguments from the left; the rest are deduced or defaulted.

Practice Attributing Arguments

EASY

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.
MEDIUM

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.

Related concepts

More concepts starting with D