Wednesday, September 16, 2009

Fun C++ Problem

Today a co-worker of mine ran into an interesting C++ inheritance problem that perplexed the usual lunch crowd. Some might call this a character flaw, but when presented with puzzles like this I feel compelled to figure them out. So, when I got back to my desk I reduced the problem to the following minimal program and got to work on figuring out the root-cause.

 1:  class Superclass
 2:  {
 3:  public:
 4:    void  foo(int aParameter);
 5:    void  foo(int  oneParameter,
 6:              int  twoParameter);
 7:  };
 8:  
 9:  class Subclass : public Superclass
10:  {
11:    void  foo(int aParameter);
12:    // inherits foo(int, int)
13:  };
14:  
15:  void Superclass::foo(int aParameter)
16:  {}
17:  
18:  void Superclass::foo(int  oneParameter,
19:                       int  twoParameter)
20:  {}
21:  
22:  void Subclass::foo(int aParameter)
23:  {
24:    foo(aParameter, 2);
25:  }
26:  
27:  int main(int argc, char * argv[])
28:  {
29:    return (0);
30:  }

In words, there is a super-class, Superclass, with an overloaded method, foo(), that has two variants - foo(int) and foo(int,int). In addition, there is a sub-class, Subclass, derived from Superclass that redefines the foo(int) variant but inherits Superclass's foo(int,int) method.

Straight forward, right? Nope.

$g++ borked.cpp 
borked.cpp: In member function ‘void Subclass::foo(int)’:
borked.cpp:26: error: no matching function for call to ‘Subclass::foo(int&, int)’
borked.cpp:24: note: candidates are: void Subclass::foo(int)

For some reason the compiler isn't able to resolve the call to the inherited foo(int,int) when called from Subclass's foo(int) method. How strange.

Commenting out the offending call on line 24 allows the program to compile and dumping the symbol table yields the expected results:

$nm a.out
0000000100000ea4 s  stub helpers
0000000100001048 D _NXArgc
0000000100001050 D _NXArgv
0000000100000e60 T __ZN10Superclass3fooEi
0000000100000e6e T __ZN10Superclass3fooEii
0000000100000e7e T __ZN8Subclass3fooEi
                 U ___gxx_personality_v0
0000000100001060 D ___progname
0000000100000000 A __mh_execute_header
0000000100001058 D _environ
                 U _exit
0000000100000e8b T _main
0000000100001020 s _pvars
                 U dyld_stub_binder
0000000100000e24 T start

All the methods are present and have unique signatures. Recompiling with the -fdump-class-hierachy option also doesn't produce any suprises - there is no vtable magic going on.

$g++ -fdump-class-hierarchy borked.cpp
$cat borked.cpp.002t.class 
Class Superclass
   size=1 align=1
   base size=0 base align=1
Superclass (0x1407c18c0) 0 empty

Class Subclass
   size=1 align=1
   base size=1 base align=1
Subclass (0x1407c1930) 0 empty
  Superclass (0x1407c19a0) 0 empty

As a final piece of the puzzle, fully qualifying the call to foo(int,int) as follows resolves the problem.

void Subclass::foo(int aParameter)
{
  Superclass::foo(aParameter, 2);
}

Now, I am no C++ guru but I do have a fair amount of experience with it - mostly from writing system level code for a proprietary RTOS that was written in C++. In fact, I wrote a loadable module framework for that RTOS that included a runtime dynamic linker capable of handling C++ intermediate object files. So, I'd like to think I have a decent knowledge of the C++ object model and yet this problem made little sense to me.

As is often the case, I didn't know the anwser to the problem but I did know enough to determine the right keywords to Google for the answer. The solution is to add a using declaration to the Sublcass's definition.

class Subclass : public Superclass
{
  using Superclass::foo;

  void  foo(int aParameter);
  // inherits foo(int, int)
};

As I now understand the situation, when a set of overloaded methods span the base and derived classes, the using declaration is required to introduce the base class's methods into the scope of the derived class. Without the using declaration, the compiler can't resolve the reference to the base class's method without fully qualifying it. Good to know!

I had a lot of fun figuring this out despite the fact that it took 20 minutes of time out of an already busy day. But, sometimes you just have to let the inner-geek exercise itself!

As an aside, I recommend Stanley Lippman's book Inside the C++ Object Model to anyone interested in learning the inner workings of the C++ object model.