Member Function Lookup



Item 24. Member Function Lookup

When you call a member function, there are three steps involved. First, the compiler looks up the name of the function. Second, it chooses the best matching function from the available candidates. Third, it checks that you have access to the matched function. That's it. Admittedly, each of these steps (especially the first two; see Argument Dependent Lookup [25, 89] and Operator Function Lookup [26, 91]) can be complex, but the overall function matching mechanism is as simple as one, two, three.

Most errors related to function matching stem not from misunderstanding the compiler's complex name lookup and overloaded function matching algorithms but from misunderstanding the simple, sequential nature of these three steps. Consider the following:

class B {
  public:
    //...
    void f( double );
};
class D : public B {
    void f( int );
};
//...
D d;
d.f( 12.3 ); // confusing

Which member f is called?

Step 1.

Look up the name of the function. Because we're calling a member of a D object, we'll start in the scope of D and immediately locate D::f.

Step 2.

Choose the best matching function from the available candidates. We have only one candidate D::f, so we attempt to match that one. We can do this by converting the actual argument 12.3 from double to int. (This is legal, but generally undesirable, because we'll lose precision.)

Step 3.

Check access. We (may) have an error, because D::f is private.

The existence of a better-matching, accessible function in the base class is immaterial, because the compiler does not continue searching for a name in outer scopes once it has found one in an inner scope. An inner scope name hides the same name in an outer scope; it does not overload it as it does in Java.

In fact, the name does not even have to be the name of a function:

class E : public D {
    int f;
};
//...
E e;
e.f( 12 ); // error!                              

In this case, we have a compile-time error because our lookup of the name f in the scope of E netted us a data member, not a member function. This is, by the way, one of many reasons to establish and adhere to a simple naming convention. If the data member E::f had been named f_ or m_f, it would not have hidden the inherited base class function f.