DekGenius.com
Team LiB   Previous Section   Next Section

5.3 Function Overloading

A single function name can have multiple declarations. If those declarations specify different function signatures, the function name is overloaded. A function call to an overloaded function requires the compiler to resolve the overloaded name and decide which function to call. The compiler uses the argument types (but not the return type) to resolve calls to overloaded functions. Example 5-11 shows simple examples of two overloaded functions named sqrt: one for int arguments and the other for double arguments. The rest of this section explains the rules for overloading and resolution.

Example 5-11. Overloaded functions
int sqrt(int);
double sqrt(double);

int main(  )
{
  std::cout << sqrt(3) << '\n';    // sqrt(int)
  std::cout << sqrt(3.14) << '\n'; // sqrt(double)
}

5.3.1 Declaring Overloaded Functions

Whenever you declare more than one function with the same name in the same scope, you are overloading the function name. The function can be an ordinary function, member function, constructor, or overloaded operator.

Overloaded functions must differ in their parameter lists: they must have a different number of parameters, or the parameter types must be different. Refer to Section 5.2.2 earlier in this chapter for information on equivalent parameter types.

Default arguments are not considered when declaring overloaded functions (but are important when resolving a call to an overloaded function, as described in Section 5.3.2). Example 5-12 shows overloaded constructors and member functions for the point class, and overloaded declarations for the add function.

Example 5-12. Overloaded constructors, member functions, and function declarations
class point {
public:
  point(int x = 0);        // Overloaded constructors
  point(int x, int y);
  point(const point& pt);
  // x and y are overloaded.
  int x(  )    const { return x_; }
  void x(int newx) { x_ = newx; }
  int y(  )    const { return y_; }
  void y(int newy) { y_ = newy; }
private:
  int x_, y_;
};

// add is overloaded.
int    add(int, int);
int    add(int);
double add(double, double);
double add(double);
long   add(long, long);
long   add(long);

// The following declarations are not overloaded, but are redeclarations of the
// add functions.
int  add(int a = 0, int b = 0);
long add(signed long, long signed);

long add(int, int); // Error: cannot overload on return type

5.3.2 Calling Overloaded Functions

A function call expression (Chapter 3) must identify the function being called, based on its name and arguments. For example, the simple expression f(x) might be a call to a function named f, a function call through a function pointer named f, a function template named f, the construction of an object of class f, a conversion of x to a type named f, or the invocation of the function call operator of an object named f. In each situation, the compiler might use different rules for interpreting f and x.

The compiler first uses the function name and context to create a list of candidate functions. The number and types of the arguments are used to select the best match from the candidates, and that function is the one that is called. If no match is found, the compiler reports an error. If more than one match ties for "best," the compiler reports an ambiguity error.

For example, the C++ standard library declares three different sqrt functions (see <cmath> in Chapter 13):

float sqrt(float);
double sqrt(double);
long double sqrt(long double);

Suppose you add another function named sqrt, such as the following, to apply it to each element of an array :

void sqrt(const double data[], size_t count);

In a function call to sqrt (e.g., sqrt(x)), the compiler first uses the ordinary rules of name lookup (Chapter 2) to find the first suitable object or function named sqrt. Suppose you used using namespace std; to import the standard sqrt functions into the same namespace as your sqrt function. The compiler would collect all the overloaded sqrt functions as the candidate list, which has four elements in this case (the three original functions plus the array version). The list is then pruned to eliminate functions with the wrong number of arguments. In this case, the array version of the function is eliminated because the expression has only one argument. Finally, the type of x is used to determine which function to call. If there is an exact match, the corresponding function is called. If x is, for example, an integer, the compiler reports an error because the three floating-point sqrt functions look equally good. If x has class type, and the class has a conversion function to one of the floating-point types, the compiler implicitly calls that conversion and then calls the corresponding function:

struct FixedPoint {
  ...
  operator long double(  );
};
void demo(  )
{
  FixedPoint x;
  std::cout << sqrt(x) << '\n'; // Prints a long double
}

If a candidate is a function template, the compiler deduces the argument types (see Chapter 7), and from that point on, the template instance is treated as a normal function.

The rules for creating the candidate list and argument list depend on the context of the function call. The argument list can also depend on context: when choosing an overloaded function to call, a member function's class is treated as an argument type. More precisely, member functions have an implicit object parameter whose type is a reference to T, in which T is the class that defines the member function. Any qualifiers on the member function also qualify the type (that is, the object type for a const function is const T&). In the function body, the type of this is a pointer to qualified T. (See Chapter 6 for more information about this.)

A call to a member function applies to a specific object, which is the implicit object argument. When calling a member function with the -> or . operator, the implicit object argument is the left operand. For an overloaded binary operator (such as operator<<), the implicit object argument is the left operand. For a unary operator, the implicit object argument is the operand. When calling an unqualified function inside a nonstatic member function, the implicit object argument is *this. The implicit object argument is considered the first argument in the argument list. Unlike normal arguments, implicit type conversions do not take place for the implicit object argument.

5.3.3 Candidate Functions

This section describes how the compiler creates its list of candidate functions. Table 5-1 summarizes the various categories of function calls, and the subsequent subsections provide the details.

Table 5-1. Function calls and candidate lists

Category

Function call syntax

Candidate functions

Qualified member function call

expr . name ( args )
expr -> name ( args )

Member functions

Unqualified function call

name ( args )
expr ( args )

Member functionsNonmember functions

Operator

expr op expr

Member functionsNonmember functions

Function-like initialization

type name ( args )

Constructors

Assignment-like initialization

type name = expr

ConstructorsType conversion operators

Conversion initialization

type name = expr

Type conversion operators

Reference bound to initialization

type& name = expr

Type conversion operators

5.3.3.1 Qualified member function call

An expression that uses the . or -> operator to call a function must call a member function. The function name is looked up in the class of the left operand and in its base classes (using the name lookup rules listed in Chapter 2). The search starts in the most-derived class. If that class declares a member function with the desired name, the search stops, and the candidate functions are all the member functions with the same name in that class. If no matching member function is found, the search continues with the immediate base classes. The search stops when a matching name is found.

In other words, a derived class cannot overload a function that is declared in a base class. Instead, if the derived class has a function with the same name, the derived class hides the name that would be inherited from the base class (or the derived class might override a virtual function; see Chapter 6). Insert a using declaration in the derived class if you want the compiler to consider the base class functions as candidates, as shown in Example 5-13.

Example 5-13. Overloading inherited functions
#include <iostream>
#include <ostream>

class base {
public:
  void f(int) { std::cout << "f(int)\n"; }
  void g(int) { std::cout << "g(int)\n"; }

};

class derived : public base {
public:
  void f(double) { std::cout << "f(double)\n"; }
  void g(double) { std::cout << "g(double)\n"; }
  using base::g; // g(int) and g(double) are visible.
};

int main(  )
{
  derived d;

  d.f(3);   // Calls derived::f(double)
  d.g(42);  // Calls base::g(int)
}

If a class has multiple immediate base classes, overload resolution must find a name in only one of the base classes. If functions with the desired name are found in multiple immediate base classes, the compiler reports an ambiguity error. To resolve this ambiguity, use the scope operator (::) to qualify the function name in the derived class, as shown in Example 5-14.

Example 5-14. Avoiding ambiguous base-class overloads
struct base1 {
  void func(int);
};
struct base2 {
  void func(double);
};
struct derived : base1, base2 {
  // Call to overloaded func is ambiguous.
  void demo1(long x) { func(x); }
  // Qualify the name to resolve ambiguity.
  void demo2(long x) { base2::func(x); }
};
5.3.3.2 Unqualified function call

An ordinary-looking function call can be a nonmember function, a member function, an object of class type, a type name, or a variable of type pointer-to-function. For a variable of type pointer-to-function, overload resolution takes place when a value is assigned to the variable (discussed later in this chapter). In the other cases, the usual name lookup rules apply when finding the candidate functions.

A function call in a member function searches first for matching member functions in the same class or an ancestor class. The search for a match begins in the class that is declaring the member function. If a match is found, candidate functions are taken from that class. If no matches are found, the search continues with ancestor classes, following the same rules as for qualified member function calls. If no matches are found in any ancestor classes, the namespace of the class is searched for nonmember functions. Example 5-15 shows how a matching member function in a base class precludes finding a better match in the global namespace.

Example 5-15. Finding candidate member functions
#include <iostream>
#include <ostream>

void proc(int x)
{
  std::cout << "proc(int:" << x << ")\n";
}

class base {
public:
  void f(int) { std::cout << "f(int)\n"; }
  void g(int) { std::cout << "g(int)\n"; }
  void proc(double) { std::cout << "base::proc(double)\n"; }
};

class derived : public base {
public:
  void f(double) { std::cout << "f(double)\n"; }
  void g(double x) {
    std::cout << "g(double)\n";
    proc(42); // Calls base::proc(double), not ::proc(int)
  }
  using base::g;
};

// Declared after derived, so call to proc(  ) inside g(  ) never sees this proc(  ).
void proc(double x)
{
  std::cout << "proc(double:" << x << ")\n";
}

int main(  )
{
  derived d;
  d.g(3.14159); // Calls g(double)
}

If a function call expression resolves to an object of class type, the class must have a function call operator or a conversion operator, in which the conversion is to a function type: pointer-to-function, reference-to-function, or reference-to-pointer-to function.

A conversion operator is rarely used. The compiler constructs a wrapper function so that the conversion function is the first argument. The conversion type followed by the types of the actual arguments is the new list of argument types to use in overload resolution. In other words, all of a class's function-type conversion operators participate in overload resolution.

Example 5-16 shows how a class-type object is used as the left operand of a function call.

Example 5-16. Calling a class-type object as a function
#include <iostream>
#include <ostream>

typedef void (*strproc)(const char*);

void print(const char* str)
{
  std::cout << "const char*:" << str << '\n';
}

void print(int x)
{
  std::cout << "int:" << x << '\n';
}

void print(double x)
{
  std::cout << "double:" << x << '\n';
}

struct simple
{
  void operator(  )(int x)    { print(x); } // print(int)
  void operator(  )(double x) { print(x); } // print(double)
};

typedef void (*intfunc)(int);
typedef void (*dblfunc)(double);

struct indirect
{
  operator intfunc(  ) { return print; } // print(int)
  operator dblfunc(  ) { return print; } // print(double)
  operator strproc(  ) { return print; } // print(const char*)
};

int main(  )
{
  simple sim;
  indirect ind;

  sim(42);                      // Prints "int:42"
  sim.operator(  )(42);         // Prints "int:42"
  sim(42.0);                    // Prints "double:42"
  ind(42);                      // Prints "int:42"
  ind.operator intfunc(  )(42); // Prints "int:42"
  ind(42.0);                    // Prints "double:42"
  ind("forty-two");             // Prints "const char*:forty-two"
}
5.3.3.3 Operator

The function for an overloaded operator is chosen according to the usual rules for resolving overloaded functions, in which the operator's operands are the function's arguments. You can overload operators only if at least one operand has a user-defined type. If all operands have built-in types, an operator has its built-in meaning.

If the left operand of an operator has class type, the operator can be a nonstatic member function or a nonmember function. Otherwise, the function must be a nonmember function. The operator function name is formed from the keyword operator followed by the operator symbol, e.g., operator[], operator++, or operator-. Unary operators can be member functions with no arguments or nonmember functions of one argument. Binary operators are member functions of one argument or nonmember functions of two arguments. Postfix increment and decrement operators are different. They are implemented as binary operators for which the right operand is an int. (See Chapter 3 for more information.)

The -> operator is also different. Although it is a binary operator, it is treated as a unary operator. It must be implemented as a member function, so the function takes no arguments. It returns a pointer or another object that overloads the -> operator. Ultimately, the overloaded operators must resolve to a pointer of class type, to which the built-in -> operator can be applied.

The candidate functions for the overloaded operator include all member, nonmember, and built-in candidate functions. Member functions do not take precedence over nonmember functions.

Example 5-17 shows how operator functions are different from named functions. In particular, it shows how operators are resolved by considering member functions and global functions, whereas named member functions take precedence over named global functions.

Example 5-17. Calling overloaded operators
class demo
{
public:
  demo(int v) : value_(v) {}

  demo add(const demo& d) const;
  demo sub(const demo& d) const;
  demo mul(const demo& d) const;
  demo operator+(const demo& d) const;
  demo operator-(const demo& d) const;
  demo operator*(const demo& d) const;
  operator int(  ) const { return value_; }
private:
  int value_;
};

// Silly examples, but illustrative
demo add(const demo& a) { return a; }
demo mul(const demo& a) { return a; }
demo div(const demo& a) { return a; }

demo operator+(const demo& a, const demo& b)
{
  return a.operator+(b); // Force use of member function.
}

demo demo::add(const demo& d)
const
{
  return *this + d; // Error: calls ::operator+(  ) or demo::operator+(  )?
}

demo demo::sub(const demo& d) const
{
  return this->operator-(d); // Member operator
}

demo demo::mul(const demo& d) const
{
   return ::operator*(*this, d); // Global operator
}

demo demo::operator+(const demo& d) const
{
  return demo(int(*this) + int(d));
}

demo demo::operator-(const demo& d) const
{
  return sub(d); // Calls demo::sub (recurses infinitely)
}

demo demo::operator*(const demo& d) const
{
  return ::mul(d); // Scopes operator to call global mul(  )
}
5.3.3.4 Function-like initialization

An object can be initialized using function-like syntax (see Chapter 2). When an object of class type is so initialized, the candidate functions are constructors of the named class. The same syntax applies to conversion initialization covered later in this chapter. Example 5-18 shows function-like initializers that call an overloaded constructor.

Example 5-18. Calling an overloaded constructor
class point {
public:
  point(  ) : x_(0), y_(0) {}
  point(int x) : x_(x), y_(0) {}
  point(int x, int y): x_(x), y_(y) {}
private:
  int x_, y_;
};

point p1(42);
point p2(4, 2);
point p3(p1); 
5.3.3.5 Assignment-like initialization

An object can be initialized using assignment-like syntax (see Chapter 2). The candidate functions for "T x = i" are single-argument, non-explicit constructors of T. If i has class type, the candidates are conversion functions that convert i to type T, as shown in Example 5-19. The compiler is free to call T's copy constructor to copy a temporary T object to x. Even if the compiler optimizes away this extra copy, T must have an accessible copy constructor.

Example 5-19. Resolving assignment-like initialization
class point {
public:
  point(  ) : x_(0), y_(0) {}
  point(int x) : x_(x), y_(0) {}
  point(int x, int y) : x_(x), y_(y) {}
  int x(  ) const { return x_; }
  int y(  ) const { return y_; }
private:
  int x_, y_;
};

class dot {
public:
  dot(int x, int y) : center_(point(x, y)) {}
  dot(const point& center) : center_(center) {}
  operator point(  ) const { return center_; }
private:
  point center_;
};

point p1 = 3;    // Invokes point(int) constructor
point p2 = 4.2;  // Converts 4.2 to 4, and invokes point(int)
dot   d1 = p1;   // Invokes dot(const point&) constructor
point p3 = d1;   // Invokes dot::operator point(  ) and implicit 
                 // point(const point&) copy constructor
5.3.3.6 Conversion initialization

An object that does not have class type can be initialized with an object of class type. The candidate functions are the conversion functions of that class type. Example 5-20 shows two cases of conversion initialization. In the first, the object c is initialized with an rbnode<int> object, n, and the compiler calls operator color( ). The second invokes operator T( ), in which T is int, to initialize i to the value 42.

Example 5-20. Initializing non-class-type objects by calling conversion functions
enum color { red, black };

template<typename T>
class rbnode {
public:
  rbnode(const T& value, color c);
  operator color(  ) const { return color_; }
  operator T(  ) const { return value_; }
private:
  T value_;
  color color_;
};

rbnode<int> n(42, black);

color c = n;
int i = n; 
5.3.3.7 Reference bound to conversion

Similar to initializing an object of non-class type with an expression of class type, you can initialize a reference to an lvalue that results from a conversion function and initialize a const reference to an rvalue. Most conversion operators do not return lvalues, so the const reference version of this rule is used more often. Example 5-21 shows some examples of binding references to conversions. The object c1 is bound to a temporary copy of n.color_, and i1 is bound directly to n.value_.

Example 5-21. Binding references to conversion lvalues
enum color { red, black };

template<typename T>
class rbnode {
public:
  rbnode(const T& value, color c);
  operator color(  )  { return color_; }
  operator T&(  )     { return value_; }
private:
  T value_;
  color color_;
};

rbnode<int> n(42, black);
const color& c1 = n;
int& i1 = n; 

5.3.4 Addresses of Overloaded Functions

When taking the address of a function, the compiler does not have the benefit of an argument list to help resolve overloading. Instead, it collects a list of candidate functions and picks one based on the type required by the context. The context can be an initializer, an assignment, a function argument, the return value of a function, or an explicit type cast.

Of the potentially matching functions, nontemplate functions are better than template functions, and more-specific template functions are better than less-specific template functions. There must be exactly one best function, or else the compiler reports an error. Example 5-22 shows simple examples of resolving the addresses of overloaded functions.

Example 5-22. Taking the address of an overloaded function
int abs(int);
long abs(long);
double abs(double);

template<typename T>
T abs(T x);

template<typename T, typename U>
T abs(T x, U = U(  ));

int main(  )
{
  int (*intfunc)(int) = &abs;            // abs(int)
  double (*dblfunc)(double) = &abs;      // abs(double)
  float (*fltfunc)(float) = &abs;        // abs<float>
  short (*shrtfunc1)(short, int) = &abs; // abs<short,int>
  short (*shrtfunc2)(short) = &abs;      // abs<short>
}

5.3.5 Best Overloaded Function

Once the compiler has found the list of candidate functions, it must choose the "best" match from the list. The candidate list is first pruned by removing functions with the wrong number of arguments. Then the remaining functions are checked to determine how to convert the actual arguments to the desired parameter types. The function with the simplest conversions wins. This section discusses these two steps.

5.3.5.1 Pruning the candidate list

Once the compiler has assembled its list of candidate functions, it prunes the list by removing functions that have the wrong number of arguments. If the function call has n argument expressions, a function is kept in the list if any of the following apply:

  • It has exactly n parameters.

  • It has fewer than n parameters followed by an ellipsis parameter.

  • It has more than n parameters, and the extra parameters have default arguments.

Also, each actual argument must be convertible (applying the rules described in the next section) to the parameter type.

Overloading Versus Default Arguments

Overloading functions can have the same effect as using default arguments. The question is when to use overloading and when to use default arguments. As usual, the answer is, "It depends."

If a default argument is complicated, you are probably better off using overloading. With default arguments, the complicated code would be duplicated at every function call. With overloading, you can concentrate it at a single point.

Ask yourself whether it makes sense to omit arguments, starting from the right. If so, you might be able to use default arguments. If some arguments cannot be omitted singly, you might need to use overloading. In the following example, omitting y without also omitting x is unusual:

void circle(float radius, float x, float y);
void circle(float radius); // x=0, y=0

If a function is complicated, you might want to use default arguments so you can write the function only once. Even if you use overloading, write a single base function and let the overloaded functions call the base function:

class shape {
public:
  // Using default arguments keeps the code simple because there is
  // only one constructor.
  explicit shape(color c = black, size s = 1);
  ...
};

5.3.5.2 Choosing the best function

Of the remaining candidate functions, the "best" function is the one that is called. If multiple functions are tied for best, the compiler reports an error. The best function is found by comparing the implicit type conversion sequences needed for each argument type (and possibly an implicit object argument). As described in the next section, some sequences are better than others. When comparing two functions, the better function is the one with the better conversion sequences:

  • If two functions A and B have argument conversion sequences that are equally good, but A has at least one argument with a better sequence, A is the better function.

  • If all the argument conversion sequences are equally good, a nontemplate function is better than a template function.

  • If the conversion sequences for two template functions are equally good, a more-specific template function is better than a less-specific template function.

5.3.5.3 Argument conversion sequences

An actual argument is implicitly converted to the desired parameter type by undergoing a series of transformations. Basically, a better conversion is one in which the argument type is closer to the parameter type and, therefore, the type undergoes fewer transformations.

A standard conversion sequence is a sequence of built-in transformations that are based on automatic type conversions (Chapter 3). A sequence is built from at most one of each of three kinds of transformations. The quality of a standard conversion sequence is dictated by its worst transformation, in which the value-preserving transformations are better than those that might discard information. The transformations are as follows, ordered from best to worst:

Lvalue transformation and qualification adjustment

An lvalue transformation is the conversion of an array to a pointer, an lvalue to an rvalue, or a function to a function pointer. A qualification adjustment is adding const or volatile qualifiers to match the parameter. These transformations are just as good as no transformation at all:

void func1(const int& x);
void func2(const char* str);
func1(42);
func2("Hello");
Promotion

The built-in type promotions are listed in Chapter 3. In general, type promotions are from smaller types to larger types, so information cannot be lost:

void func3(long l);
func3(42);
Conversion

A built-in type conversion is one that might lose information, such as converting a number to a smaller type, or a floating point to an integer. Certain conversions are better than others:

  • A conversion of a pointer to bool is worse than other conversions.

  • Converting a derived-class pointer to a base-class pointer is better than converting the pointer to void*.

  • Converting a derived-class pointer to void* is better than converting a base-class pointer to void*.

  • When converting classes, class pointers, and pointers to members, shorter distances in a class hierarchy are better than longer distances; for example, if A inherits from B and B inherits from C, A* to B* is better than A* to C*.

The following are some examples of conversion transformations:

void func4(bool b);
func4(3.14);
void func5(void* ptr);
func5(&x);

A user-defined conversion sequence has up to three parts: a standard conversion sequence followed by a single, user-defined conversion (constructor or type conversion operator) followed by another standard conversion sequence.

A standard conversion sequence is better than a user-defined conversion sequence. A user-defined conversion sequence is better than matching an ellipsis parameter.

If one sequence of transformations is a subsequence of another, the shorter sequence is better than the longer one.

Among sequences that differ only by qualification, the one with fewer qualification adjustments is better than one with more adjustments.

Example 5-23 shows how overloaded functions are chosen. Notice that type promotions are preferred to the conversion to Num, even though Num has an exact type conversion to long. Also note how an unsigned long cannot be promoted, so it must undergo a built-in conversion. The compiler has no preference of int, long or double, so the conversion is ambiguous and, therefore, results in an error. The same applies to long double; even though you might consider the conversion to double to be "better" than conversion to int, the rules of overloading say otherwise. The final example results in an error because there is no matching function. An array of wchar_t cannot be converted to any of the types used as func parameters.

Example 5-23. Resolving overloaded functions
#include <string>

void func(double);
void func(long);
void func(int);
void func(const std::string&);

class Num {
public:
  Num(int i) : num_(i) {}
  operator long(  ) const { return num_; }
private:
  int num_;
};

int main(  )
{
  short n = 42;
  func(n);           // func(int)
  func(Num(n));      // func(long)
  func(true);        // func(int)
  func(3.1415f);     // func(double)
  func("string");    // func(string);
  std::string s("string");
  func(s);           // func(string);
  func(3.14159L);    // Error: ambiguous
  func(42UL);        // Error: ambiguous
  func(L"widestr");  // Error: no match
}
    Team LiB   Previous Section   Next Section