9.10 FUNCTION OVERLOAD RESOLUTION IN C


9.10 FUNCTION OVERLOAD RESOLUTION IN C++

Stated simply, overloading a function name, a constructor name, or an operator symbol means being able to use the same name with a different number and/or types of arguments. In this section, we will discuss the overloading of function and constructor names in C++.

To see why overloading a constructor might be useful, suppose you define an Employee class with data members name, title, level, contactInfo, and so on, as shown below. For employees for whom the information is available for all the data members, you would want to provide a constructor that has parameters for all the data members. But if, say, the contact information was not currently available for a new employee, it would be convenient if you also provided a constructor that did not require this information. And, if most of the new employees are hired with the title "memberOfStaff", it would be a nice feature to make available a constructor that does not have a parameter for title for such employees. Considerations such as these could lead to a class design with multiple constructors as shown below:

 
class Employee { string name; string title; short level; string contactInfo; public: //first constructor: Employee () {} //second constructor: Employee (string aName, string aTitle, short aLevel, string cInfo) { name = aName; title = aTitle; level = aLevel; contactInfo = cInfo; } //third constructor: Employee (string aName, string aTitle, short aLevel) { name = aName; title = aTitle; level = aLevel; contactInfo = "not available"; } //fourth constructor: Employee(string aName, string aTitle) { name = aName; title = aTitle; level = 9; contactInfo = "not available"; } //fifth constructor: Employee (string aName) { name = aName; title = "memberOfStaff"; level = 9; contactInfo = "not available"; } // more constructors if needed in the rest of the code };

As an example illustrating the usefulness of overloading a function name, you could define a Student class with multiple versions of calculateTuition method, as shown below:

 
class Student { Name name; Date birthDate; bool inState; StudentStatus status; //.. public: //. double calculateTuition( bool inStateStatus, int numOfCourses, StudentStatus aStatus); //(A) double calculateTuition( bool inStateStatus, StudentStatus aStatus); //(B) double calculateTuition(); //(C) // . };

The first function in line (A) could be used for part-time and temporary students whose fees are calculated on the basis of how many courses they register for, in addition to considerations such as their in-state residence status, and so on. The second version, in line (B), would be good for students carrying a full load where the number of courses is immaterial. And the last version, in line (C), would be useful for situations where someone wants to know the basic fee structure for attending a university during a given semester.

Having the compiler select the most appropriate constructor or the most appropriate function from amongst the choices available is straightforward when the number of parameters is different. But choosing the right constructor or the right function is not so straightforward if the different constructors and functions differ with respect to just the parameter types.

For example, let's say we have a set of math functions of the following prototypes, all functions calculating the same function but with different types of arguments:

      int foo(int x, int y);                               //(D)      double foo(double x, int y);                         //(E)      int foo(int x, double y);                            //(F) 

Now let's say that we have the following function invocation in a computer program:

      float u;      float v;      float w = foo(u, v); 

Now it is not so straightforward to say which of the three choices in lines (D), (E), and (F) would be the right function to use for the invocation f(u, v). And, after we have specified a set of criteria for choosing the best applicable function, what if it turns out that we have more than one "best" function as a candidate, as seems to be the case with the above example? What should the compiler do then?

Given more than one function definition to choose from for a given function call, selecting the most appropriate function definition is called overload resolution. Overload resolution also refers to choosing the most applicable constructor if more than one choice is available.

Both C++ and Java compilers come equipped with algorithms for overload resolution. For overload resolution, a function is represented by its signature, which is the name of the function together with its parameter list specifying how many parameters and what type to expect for each parameter position. It is important to bear in mind that the return type from a function or the type of exceptions thrown by a function do not contribute to the signature of a function and, therefore, do not play a role in overload resolution.[6]

Overload resolution algorithms, in general, are based on the notion of the specificity of a match between the type of an argument in a function call and the type of the corresponding parameter in the function definition. When the two types are exactly the same, that is the most specific match one can hope to achieve. But when type conversions are required for matching, we can have matches at different levels of specificity. In the rest of this section, we will talk about these different levels of specificity in C++'s overload resolution.

Here are the different levels of specificity, reproduced from [54], with which the arguments in a function call can be matched with the corresponding parameters in a function definition in C++:

  • Specificity Level 1: In matching a single argument with the corresponding parameter, if the two types agree exactly or after trivial type conversion, we have the most specific match. Examples of trivial type conversions are array name to pointer, function name to pointer-to-function, and T to const T.

  • Specificity Level 2: If the type conversion between the argument type and the parameter type requires a promotion, we have a less specific match than the previous one. As defined in Section 6.7.1 of Chapter 6, a type conversion is a promotion if (a) the integral types stay integral; (b) the non-integral types stay non-integral; and (c) there is no loss of information.

  • Specificity Level 3: Matching takes place at a level of specificity lower than the previous one if it entails a standard conversion. As discussed earlier in Section 6.7.1 of Chapter 6, examples of standard conversions for the primitive types include int to double, double to int, and so on. For pointer types, standard conversion include T* to void*, int* to unsigned int*, and so on. And for class types in general, a conversion from Derived* to Base* is a standard conversion. The last conversion means that it is permissible to convert a pointer to an object of a derived class into a pointer of the base class type. The reader will recall that one special feature of object-oriented programming is polymorphism, which means that an object of a subclass type can always be considered to be an object of the superclass type (when manipulated through pointers and references in C++).

  • Specificity Level 4: A still lower level of specificity in matching takes place if it requires a user-defined type conversion. When a new class is defined, one can also specify what type conversions should be allowed to occur automatically for this class.[7]

  • Specificity Level 5: Match using the ellipsis in a function definition results in a least specific match. The reader will recall from C programming that an ellipsis at the end of the parameter list of a function allows an indefinite number of arguments to be matched to it.

Overload resolution chooses that function which supplies the most specific match. If more than one function is found to match at the highest possible level of specificity, an ambiguity is declared, resulting in a compile-time error.

Recall again that only the signature of a function is allowed to participate in the overload resolution algorithm. Since the return type and the types of exceptions thrown play no role in a signature, overload resolution does not depend on them. Overload resolution is also independent of the order in which the functions are declared.

Finally, functions declared in different scopes do not overload. Consider this adaptation of an example from [54] in which we have two functions of the same name, g(int) in line (C) and g(double) in line (D), with the prototype of the latter declared inside another function, f(), in line (A). Ordinarily, the function call in line (B) would cause the compiler to apply the overload resolution algorithm to the two definitions of g in lines (C) and (D). But since the version g(double) is declared inside the function f(), where it hides the function g(int) declared in the global scope, no overload resolution is needed. Of the two versions of g, g(double) is the only function in scope inside f().

 
//DiffScope.cc #include<iostream> using namespace std; void f(); void g(int); int main() { f(); return 0; } void f() { void g(double); // function prototype declared inside f() //(A) g(1); //(B) } void g(int x) { cout << "g(int): x is" << x << endl; } //(C) void g(double x) { cout << "g(double): x is" << x << endl; } //(D)

The program produces the output:

      from g(double): x is 1 

[6]That the return type of a function should play no role in overload resolution makes sense because functions are frequently invoked for just their side effect behavior and their return values ignored.

[7]Automatic type conversions for user-defined classes is presented in Section 12.9 of Chapter 12.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net