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 Tand one for ParamType . These types are frequently different, because ParamType often contains adornments, e.g., constor 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 Tis the same as the type of the argument passed to the function, i.e., that Tis the type of expr . In the above example, that's the case: xis an int, and Tis deduced to be int. But it doesn't always work that way. The type deduced for Tis 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 paramand Tin 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 cxand rxdesignate constvalues, Tis deduced to be const int, thus yielding a parameter type of const int&. That's important to callers. When they pass a constobject 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 constobject to a template taking a T& parameter is safe: the constness 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, Tis 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 constness of cxand rxcontinues to be respected, but because we're now assuming that param is a reference-to- const, there's no longer a need for constto 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 paramwere 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 Tis 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
Читать дальше