Overloading Functions

A powerful feature of C++ is the ability to use the same name for several functions. This sounds like a confusing thing to do, but when people draw water and draw a card the meaning of 'draw' is usually obvious from the context. In the same way, C++ uses the argument types to distinguish between the various overloaded functions.

sqr()

The sqr(x) function squares its argument. This operation makes sense for any argument that you can multiply by itself (for instance, both floating-point and integer numbers). Bear in mind that floating-point and integer arithmetic are very different on a machine level, and using a double sqr(double) function to do integer squaring can be very inefficient. So you need to define two functions, sqr() and sqr_int(), but the marvelous thing about C++ functions is that you can use the same name for both functions. Here's how it works:

;> double sqr(double x) {  return x*x; }
;> int sqr(int I) {  return I*I; }
;> sqr(2.0);
(double) 4.
;> sqr(2);
(int) 4

double sqr(double) and int sqr(int) are two very distinct functions; their implementation may look similar, but floating-point multiplication is different from integer multiplication, even though you use * for both types. The operator * is in fact itself overloaded. The compiler can distinguish the correct function to use because the double argument is distinct from the int argument. We say that the signature—that is, the set of arguments plus the return type—of the functions is different. The correct function is resolved from the overloaded set of functions with the same name. And you can continue to add functions called sqr() to the overloaded set providing their arguments are sufficiently different. For example, some like to define squaring of a vector to mean the vector of squared values:

;> typedef vector<double> Dvect;
;> Dvect sqr(const Dvect& v) {
;1}  Dvect tmp = v;
;1}  for(int k=0,n=v.size(); k < n; k++)
;2}    tmp[k] = sqr(tmp[k]);
;1}  return tmp;
;1}  }

It appears as if this version of sqr() is defined in terms of itself, but this isn't so: sqr(tmp[k]) has an argument of type double, which matches sqr(double); and that is a different function. This appears marvelous, but there is a downside. First, if there are separate sqr() functions for int and double, you cannot depend on the usual int-to-double conversion: The system picks the best fit and doesn't try to force the int argument into a double function. You can always force the function you want by explicitly writing floating-point numbers (for example, 2. or 2.0) or by using a typecast (for example, sqr((double)k)). Also, the signatures need to be sufficiently different from one another. If the compiler finds two or more functions that match equally well, an error results. Say that you defined the floating-point sqr() by using float, not double:

;> float sqr(float x) {  return x*x; }
;> sqr(1.2);
CON 4:Ambiguous match for void sqr(const double)
    int sqr(int)
    float sqr(float)

To begin with, a constant like 1.2 has type double. People tend to instinctively feel that float is somehow closer to double than it is to int, but this is not necessarily so. It is true that a float will be promoted to a double, and the system will prefer this to doing a standard conversion from int to double. But both double-to-int and double-to-float are narrowing operations in which information could be lost (often called demotion) because 8 bytes of precision is being forced into 4 bytes. So double-to-int and double-to-float are both considered standard conversions and are equally favored. Both versions of sqr() will then match a double argument, and this is called an ambiguous match. You should prefer double arguments to float arguments for this reason.

An ambiguous match error is not actually a bad thing; it is worse when the system silently picks the wrong function without any warning. (Error messages are very useful when it comes to getting programs right, and everyone gets compile errors when writing serious programs.) These potential problems may make you feel nervous about using overloaded functions. However, you don't need to know all the rules to use overloaded functions effectively. Keep the argument types as distinct as possible, and for the most part, things should behave themselves.

Functions can be distinguished by the numbers as well as by the types of their arguments. Distinguishing them by number is the best way of keeping functions distinct and unambiguous. For example, these are clearly different signatures because they have a different number of arguments:

;> int minv(char arr[], int sz);
;> int minv(char ptr[]);
						

Different Parameters for the Same Operation

The basic question to ask when naming a function is this: Does this name make the function's purpose clear to some future user? If it is difficult to name a function, then maybe the function's purpose is confused anyway; perhaps the function needs to be broken up into two or more distinct operations. This is particularly true when functions are overloaded. You reuse names when a name describes a distinct operation that is applicable to different arguments. You could probably write a whole program in which all the functions (except for main(), of course) were called function(), and this would be a bad thing not only because the names are not descriptive but because there is no common operation. Generally, it's a good idea to keep names distinct.

An Alternative to Default Values

Sometimes it is unnecessary to overload functions because default arguments will do the job better than overloaded functions. A good example is the make_point() function discussed earlier in this chapter. You could achieve the same effect as that make_point() function by using two functions, declared like this:

;> Point make_point(int x, int y);
;> Point make_point();
						

If you supply definitions for these functions, you will see that the two implementations are practically the same—they aren't really different functions at all. You can use both default arguments and overloaded functions, but be aware that they sometimes collide with each other. If you supplied default arguments for the first function in the preceding example, the overload resolution would become ambiguous because there are two identical ways of matching the same signature:

;> Point make_point(int x=0, int y=0);
;> Point make_point();
;>  make_point();
CON 16:Ambiguous match for void make_point()
    Point make_point(int,int)
    Point make_point()

Of course, you can change your mind in the future about whether to use default arguments or function overloading because the effect is the same. Generally, you should use default arguments if the implementation of the other case would be almost identical, like with the preceding two versions of make_point(). But if the implementation is different, then overloading would be better. You should use overloading only if the functions have very different ways of doing the same thing; for example, print(Point) and print(Date) would be good candidates.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.8.8