Team LiB
Previous Section Next Section

6.6. Function Matching

 
Image

In 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)

 

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 doubles might be viable. Either of these functions can be called with a single argument. The function taking two doubles has a default argument, which means it can be called with a single argument.

 

Image Note

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 type double to the parameter of type int.

 

f(double, double) is viable because a default argument is provided for the function’s second parameter and its first parameter is of type double, which exactly matches the type of the parameter.

 

Image Note

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.

 

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

 

 

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

 
Image

In 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:

 

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

 

2. Match through a const conversion (§ 4.11.2, p. 162).

 

3. Match through a promotion (§ 4.11.1, p. 160).

 

4. Match through an arithmetic (§ 4.11.1, p. 159) or pointer conversion (§ 4.11.2, p. 161).

 

5. Match through a class-type conversion. (§ 14.9 (p. 579) covers these conversions.)

 
Matches Requiring Promotion or Arithmetic Conversion
 
Image

Image Warning

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

 

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);

 

 
Team LiB
Previous Section Next Section