6.6. Function Matching
AdvancedIn many (if not most) cases, it is easy to figure out which overloaded function matches a given call. However, it is not so simple when the overloaded functions have the same number of parameters and when one or more of the parameters have types that are related by conversions. As an example, consider the following set of functions and function call:
void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6); // calls void f(double, double)
INFO
Exercises Section 6.5.3
Exercise 6.47: Revise the program you wrote in the exercises in § 6.3.2 (p. 228) that used recursion to print the contents of a vector
to conditionally print information about its execution. For example, you might print the size of the vector
on each call. Compile and run the program with debugging turned on and again with it turned off.
Exercise 6.48: Explain what this loop does and whether it is a good use of assert
:
string s;
while (cin >> s && s != sought) { } // empty body
assert(cin);
Determining the Candidate and Viable Functions
The first step of function matching identifies the set of overloaded functions considered for the call. The functions in this set are the candidate functions. A candidate function is a function with the same name as the called function and for which a declaration is visible at the point of the call. In this example, there are four candidate functions named f
.
The second step selects from the set of candidate functions those functions that can be called with the arguments in the given call. The selected functions are the viable functions. To be viable, a function must have the same number of parameters as there are arguments in the call, and the type of each argument must match—or be convertible to—the type of its corresponding parameter.
We can eliminate two of our candidate functions based on the number of arguments. The function that has no parameters and the one that has two int
parameters are not viable for this call. Our call has only one argument, and these functions have zero and two parameters, respectively.
The function that takes a single int
and the function that takes two double
s might be viable. Either of these functions can be called with a single argument. The function taking two double
s has a default argument, which means it can be called with a single argument.
INFO
When a function has default arguments (§ 6.5.1, p. 236), a call may appear to have fewer arguments than it actually does.
Having used the number of arguments to winnow the candidate functions, we next look at whether the argument types match those of the parameters. As with any call, an argument might match its parameter either because the types match exactly or because there is a conversion from the argument type to the type of the parameter. In this example, both of our remaining functions are viable:
f(int)
is viable because a conversion exists that can convert the argument of typedouble
to the parameter of typeint
.f(double, double)
is viable because a default argument is provided for the function’s second parameter and its first parameter is of typedouble
, which exactly matches the type of the parameter.
INFO
If there are no viable functions, the compiler will complain that there is no matching function.
Finding the Best Match, If Any
The third step of function matching determines which viable function provides the best match for the call. This process looks at each argument in the call and selects the viable function (or functions) for which the corresponding parameter best matches the argument. We’ll explain the details of “best” in the next section, but the idea is that the closer the types of the argument and parameter are to each other, the better the match.
In our case, there is only one (explicit) argument in the call. That argument has type double
. To call f(int)
, the argument would have to be converted from double
to int
. The other viable function, f(double, double)
, is an exact match for this argument. An exact match is better than a match that requires a conversion. Therefore, the compiler will resolve the call f(5.6)
as a call to the function that has two double
parameters. The compiler will add the default argument for the second, missing argument.
Function Matching with Multiple Parameters
Function matching is more complicated if there are two or more arguments. Given the same functions named f
, let’s analyze the following call:
f(42, 2.56);
The set of viable functions is selected in the same way as when there is only one parameter. The compiler selects those functions that have the required number of parameters and for which the argument types match the parameter types. In this case, the viable functions are f(int, int)
and f(double, double)
. The compiler then determines, argument by argument, which function is (or functions are) the best match. There is an overall best match if there is one and only one function for which
- The match for each argument is no worse than the match required by any other viable function
- There is at least one argument for which the match is better than the match provided by any other viable function
If after looking at each argument there is no single function that is preferable, then the call is in error. The compiler will complain that the call is ambiguous.
In this call, when we look only at the first argument, we find that the function f(int, int)
is an exact match. To match the second function, the int
argument 42
must be converted to double
. A match through a built-in conversion is “less good” than one that is exact. Considering only the first argument, f(int, int)
is a better match than f(double, double)
.
When we look at the second argument, f(double, double)
is an exact match to the argument 2.56
. Calling f(int, int)
would require that 2.56
be converted from double
to int
. When we consider only the second parameter, the function f(double, double)
is a better match.
The compiler will reject this call because it is ambiguous: Each viable function is a better match than the other on one of the arguments to the call. It might be tempting to force a match by explicitly casting (§ 4.11.3, p. 162) one of our arguments. However, in well-designed systems, argument casts should not be necessary.
TIP
Best Practices
Casts should not be needed to call an overloaded function. The need for a cast suggests that the parameter sets are designed poorly.
INFO
Exercises Section 6.6
Exercise 6.49: What is a candidate function? What is a viable function?
Exercise 6.50: Given the declarations for f
from page 242, list the viable functions, if any for each of the following calls. Indicate which function is the best match, or if the call is illegal whether there is no match or why the call is ambiguous.
(a)f(2.56, 42)
(b)f(42)
(c)f(42, 0)
(d)f(2.56, 3.14)
Exercise 6.51: Write all four versions of f
. Each function should print a distinguishing message. Check your answers for the previous exercise. If your answers were incorrect, study this section until you understand why your answers were wrong.
6.6.1. Argument Type Conversions
AdvancedIn order to determine the best match, the compiler ranks the conversions that could be used to convert each argument to the type of its corresponding parameter. Conversions are ranked as follows:
- An exact match. An exact match happens when:
- The argument and parameter types are identical.
- The argument is converted from an array or function type to the corresponding pointer type. (§ 6.7 (p. 247) covers function pointers.)
- A top-level
const
is added to or discarded from the argument. - Match through a
const
conversion (§ 4.11.2, p. 162). - Match through a promotion (§ 4.11.1, p. 160).
- Match through an arithmetic (§ 4.11.1, p. 159) or pointer conversion (§ 4.11.2, p. 161).
- Match through a class-type conversion. (§ 14.9 (p. 579) covers these conversions.)
Matches Requiring Promotion or Arithmetic Conversion
TrickyWARNING
Promotions and conversions among the built-in types can yield surprising results in the context of function matching. Fortunately, well-designed systems rarely include functions with parameters as closely related as those in the following examples.
In order to analyze a call, it is important to remember that the small integral types always promote to int
or to a larger integral type. Given two functions, one of which takes an int
and the other a short
, the short
version will be called only on values of type short
. Even though the smaller integral values might appear to be a closer match, those values are promoted to int
, whereas calling the short
version would require a conversion:
void ff(int);
void ff(short);
ff('a'); // char promotes to int; calls f(int)
All the arithmetic conversions are treated as equivalent to each other. The conversion from int
to unsigned int
, for example, does not take precedence over the conversion from int
to double
. As a concrete example, consider
void manip(long);
void manip(float);
manip(3.14); // error: ambiguous call
The literal 3.14
is a double
. That type can be converted to either long
or float
. Because there are two possible arithmetic conversions, the call is ambiguous.
Function Matching and const
Arguments
When we call an overloaded function that differs on whether a reference or pointer parameter refers or points to const
, the compiler uses the const
ness of the argument to decide which function to call:
Record lookup(Account&); // function that takes a reference to Account
Record lookup(const Account&); // new function that takes a const reference
const Account a;
Account b;
lookup(a); // calls lookup(const Account&)
lookup(b); // calls lookup(Account&)
In the first call, we pass the const
object a
. We cannot bind a plain reference to a const
object. In this case the only viable function is the version that takes a reference to const
. Moreover, that call is an exact match to the argument a
.
In the second call, we pass the nonconst
object b
. For this call, both functions are viable. We can use b
to initialize a reference to either const
or nonconst
type. However, initializing a reference to const
from a nonconst
object requires a conversion. The version that takes a nonconst
parameter is an exact match for b
. Hence, the nonconst
version is preferred.
Pointer parameters work in a similar way. If two functions differ only as to whether a pointer parameter points to const
or nonconst
, the compiler can distinguish which function to call based on the const
ness of the argument: If the argument is a pointer to const
, the call will match the function that takes a const*
; otherwise, if the argument is a pointer to nonconst
, the function taking a plain pointer is called.
INFO
Exercises Section 6.6.1
Exercise 6.52: Given the following declarations,
void manip(int, int);
double dobj;
what is the rank (§ 6.6.1, p. 245) of each conversion in the following calls?
(a)manip('a', 'z');
(b)manip(55.4, dobj);
Exercise 6.53: Explain the effect of the second declaration in each one of the following sets of declarations. Indicate which, if any, are illegal.
(a)int calc(int&, int&);
int calc(const int&, const int&);
(b)int calc(char*, char*);
int calc(const char*, const char*);
(c)int calc(char*, char*);
int calc(char* const, char* const);