Overloading in C++

Although C++ does not provide support for subtyping, it does provide extensive support for function overloading. Given a collection of functions (overloading a particular function name) C++ employs a system of matches to select the function that is most appropriate for a particular call.

Overloaded function selection rules

\zline{\fbox{C++}}

Multiple arguments

-- intersect rule
slide: Overloading in C++

Matches may involve built-in or user-defined conversions. The general rule underlying the application of conversions is that {\em conversions that are considered less error-prone and surprising are to be preferred over the others}. This rule is reflected in the ordering of the C++ overloading selection rules depicted in slide 9-cc-over. According to the rules, the absence of conversions is to be preferred. For compatibility, with C, array to pointer conversions are applied automatically, and also T to const T conversions are considered as unproblematic. Next, we have the integral promotion rules, allowing for the conversion of char to int and short to int, for example. These conversions are also directly inherited from C, and are safe in the sense that no information loss occurs. Further, we have the standard conversions such as int to double and derived* to base*, user-defined conversions (as determined by the definition of one-argument constructors and conversion operators), and the ... ellipsis notation, which allows us to avoid type-checking in an arbitrary manner. For selecting the proper function from a collection of overloaded functions with multiple arguments, the so-called intersect rule is used, which states that the function is selected with a better match for at least one argument and at least as good a match for every other argument. In the case that no winner can be found because there are multiple candidate functions with an equally good match, the compiler issues an error, as in the example below
  void f(int, double);
  void f(double, int);
  
  f(1,2.0); 
f(int, double);
f(2.0,1);
f(double,int);
f(1,1);
error: ambiguous

slide: example

The reason that C++ employs a system of matches based on declarations and actual parameters of functions is that the graph of built-in conversions (as inherited from C) contains cycles. For example, implicit conversions exist from int to double and double to int (although in the latter case the C++ compiler gives a warning). Theoretically, however, the selection of the best function according to the subtype relation would be preferable. However, the notion of best is not unproblematic in itself. For example, consider the definition of the overloaded function f and the classes P and C in slide 9-cc-best.
  class P;
  class C;
  
  void f(P* p) { cout << "f(P*)"; } 
\c{// (1)}
void f(C* c) { cout << "f(C*)"; }
\c{// (2)}
class P { public: virtual void f() { cout << "P::f"; }
\c{// (3)}
}; class C : public P { public: virtual void f() { cout << "C::f"; }
\c{// (4)}
};

slide: Static versus dynamic selection

What must be considered the best function f, given a choice between (1), (2), (3) and (4)?
  P* p = new P; 
static and dynamic P*
C* c = new C;
static and dynamic C*
P* pc = new C;
stat\c{ic} P*, dyna\c{mic} C*
f(p);
f(P*)
f(c);
f(C*)
f(pc);
f(P*)
p->f();
P::f
c->f();
C::f
pc->f();
C::f

slide: Example

In the example given above, we see that for the functions f (corresponding to (1) and (2)) the choice is determined by the static type of the argument, whereas for the member functions f (corresponding to (3) and (4)) the choice is determined by the dynamic type. We have a dilemma. When we base the choice of functions on the dynamic type of the argument, the function subtype refinement rule is violated. On the other hand, adhering to the domain contravariance property seems to lead to ignoring the potentially useful information captured by the dynamic type of the argument.