If you're willing to overlook a pinch of pseudocode, we can think of a function template as looking like this:
templateT>
void f( ParamType param);
A call can look like this:
f( expr ); // call f with some expression
During compilation, compilers use expr
to deduce two types: one for T
and one for ParamType
. These types are frequently different, because ParamType
often contains adornments, e.g., const
or reference qualifiers. For example, if the template is declared like this,
template
void f( const T¶m); // ParamType is const T&
and we have this call,
int x = 0;
f( x); // call f with an int
T is deduced to be int
, but ParamType
is deduced to be const int&
.
It's natural to expect that the type deduced for T
is the same as the type of the argument passed to the function, i.e., that T
is the type of expr
. In the above example, that's the case: x
is an int
, and T
is deduced to be int
. But it doesn't always work that way. The type deduced for T
is dependent not just on the type of expr
, but also on the form of ParamType
. There are three cases:
• ParamType
is a pointer or reference type, but not a universal reference. (Universal references are described in Item 24. At this point, all you need to know is that they exist and that they're not the same as lvalue references or rvalue references.)
• ParamType
is a universal reference.
• ParamType
is neither a pointer nor a reference.
We therefore have three type deduction scenarios to examine. Each will be based on our general form for templates and calls to it:
templateT>
void f( ParamType param);
f( expr ); // deduce T and ParamType from expr
Case 1: ParamType
is a Reference or Pointer, but not a Universal Reference
The simplest situation is when ParamType
is a reference type or a pointer type, but not a universal reference. In that case, type deduction works like this:
1. If expr
's type is a reference, ignore the reference part.
2. Then pattern-match expr
's type against ParamType
to determine T
.
For example, if this is our template,
template
void f( T¶m); // param is a reference
and we have these variable declarations,
int x = 27; // x is an int
const int cx = x; // cx is a const int
const int& rx = x; // rx is a reference to x as a const int
the deduced types for param
and T
in various calls are as follows:
f(x); // T is int , param's type is int&
f(cx); // T is const int ,
// param's type is const int&
f(rx); // T is const int ,
// param's type is const int&
In the second and third calls, notice that because cx
and rx
designate const
values, T
is deduced to be const int
, thus yielding a parameter type of const int&
. That's important to callers. When they pass a const
object to a reference parameter, they expect that object to remain unmodifiable, i.e., for the parameter to be a reference-to- const
. That's why passing a const
object to a template taking a T& parameter is safe: the const
ness of the object becomes part of the type deduced for T
.
In the third example, note that even though rx
's type is a reference, T
is deduced to be a non-reference. That's because rx
's reference-ness is ignored during type deduction.
These examples all show lvalue reference parameters, but type deduction works exactly the same way for rvalue reference parameters. Of course, only rvalue arguments may be passed to rvalue reference parameters, but that restriction has nothing to do with type deduction.
If we change the type of f
's parameter from T&
to const T&
, things change a little, but not in any really surprising ways. The const
ness of cx
and rx
continues to be respected, but because we're now assuming that param is a reference-to- const
, there's no longer a need for const
to be deduced as part of T
:
template
void f( const T¶m); // param is now a ref-to- const
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // T is int , param's type is const int&
f(cx); // T is int , param's type is const int&
f(rx); // T is int , param's type is const int&
As before, rx
's reference-ness is ignored during type deduction.
If param
were a pointer (or a pointer to const
) instead of a reference, things would work essentially the same way:
template
void f( T*param); // param is now a pointer
int x = 27; // as before
const int *px = &x; // px is a ptr to x as a const int
f(&x); // T is int, param's type is int*
f(px); // T is const int ,
// param's type is const int*
By now, you may find yourself yawning and nodding off, because C++'s type deduction rules work so naturally for reference and pointer parameters, seeing them in written form is really dull. Everything's just obvious! Which is exactly what you want in a type deduction system.
Case 2: ParamType
is a Universal Reference
Things are less obvious for templates taking universal reference parameters. Such parameters are declared like rvalue references (i.e., in a function template taking a type parameter T
, a universal reference's declared type is T&&
), but they behave differently when lvalue arguments are passed in. The complete story is told in Item 24, but here's the headline version:
• If expr
is an lvalue, both T and ParamType
are deduced to be lvalue references. That's doubly unusual. First, it's the only situation in template type deduction where T
is deduced to be a reference. Second, although ParamType
is declared using the syntax for an rvalue reference, its deduced type is an lvalue reference.
• If expr
is an rvalue, the “normal” (i.e., Case 1) rules apply.
For example:
template
void f( T&¶m); // param is now a universal reference
int x = 27; // as before
const int cx = x; // as before
Читать дальше