C++ multiple inheritance and Objective-C. Is this is a bug in GCC

ciphonemacosobjective c

I encountered following weird behavior yesterday. It seems a compiler bug to me or is there a something that I've missed? I was wrapping Facebook Connect for iPhone's Objective-C classes with Objective-C to C++ adaptor classes, so that they could be used from our own OpenGL/C++ code more conveniently.

The following code reveals the problem. In the first variant below, the compiler compiles but messes up the vtables and thus wrong method is called. In the second variant, we get a compiler error which indicates that gcc is confused.

Comments try to explain the situation in more detail.

#include <iostream>
#import <Foundation/Foundation.h>

// An abstract C++ interface
class Foo_cpp {
public:
    virtual void foo() = 0;
};

// Another abstract C++ interface
class Bar_cpp {
public:
    virtual void bar() = 0;
};


// An Objective-C to C++ adaptor. 
// It takes a C++ interface Foo. When it's do_foo method is called it
// delegates call to Foo::foo.
@interface Foo_objc : NSObject {
    Foo_cpp* foo_cpp_;
}
@end

@implementation Foo_objc

- (id)init:(Foo_cpp*)foo {
    self = [super init];
    if (self) {
        foo_cpp_ = foo;
    } 
    return self;
}

- (void) do_foo {
    std::cout << "do_foo: ";
    foo_cpp_->foo();
}
@end 

// Another Objective-C to C++ adaptor. 
@interface Bar_objc : NSObject{
    Bar_cpp* bar_cpp_;
}
@end 

@implementation Bar_objc

- (id)init:(Bar_cpp*)bar {
    self = [super init];
    if (self) {
        bar_cpp_ = bar;
    }
    return self;
}

- (void) do_bar {
    std::cout << "do_bar: ";
    bar_cpp_->bar();
}
@end 

// Main class implements both abstract C++ interfaces (which will
// confuse the compiler as we shall see). 
// It constructs two Objective-C to C++ adaptors as a members and
// tries to pass itself as a C++ delegate for these adaptors.
class Main : public Foo_cpp, public Bar_cpp {
public:
    Foo_objc* foo_;
    Bar_objc* bar_;

    Main() {
        // We try to construct two objective-c to c++ adaptors Foo_objc and
        // Bar_objc. 
        // 
        // We expect output of 
        // [foo_ do_foo];
        // [bar_ do_bar];
        // to be
        //   do_foo: foo
        //   do_bar: bar
#if 0
        // This variant compiles but the compiler messes up
        // the vtables. When do_bar() is called, we expect
        // bar() to be called via Bar_objc, but instead
        // foo() is called from both adaptors.
        // Output is
        //    do_foo: foo
        //    do_bar: foo  !!!! Calls wrong method !!!!
        foo_ = [[Foo_objc alloc] init:this];
        bar_ = [[Bar_objc alloc] init:this];

        [foo_ do_foo];
        [bar_ do_bar];
#else 
        // Now, this variant tries to help the compiler by passing 
        // |this| via a variable of the correct interface type.
        // It actually reveals the confusion that the compiler
        // is having. Seems like a bug in the compiler.
        Foo_cpp* iface = this;
        foo_ = [[Foo_objc alloc] init:iface];

        Bar_cpp* iface2 = this;
        // Error we get is on the next code line.
        //   $ g++ -x objective-c++ -lobjc mheritance_test.mm
        //   mheritance_test.mm: In constructor ‘Main::Main()’:
        //   mheritance_test.mm:107: error: cannot convert ‘Bar_cpp*’ to ‘Foo_cpp*’ in argument passing
        bar_ = [[Bar_objc alloc] init:iface2];

        [foo_ do_foo];
        [bar_ do_bar];
#endif

    }

    ~Main() {
        delete foo_;
        delete bar_;
    }

    virtual void foo() {
        std::cout << "foo" << std::endl;
    }

    virtual void bar() {
        std::cout << "bar" << std::endl;
    }

};

int main() {
    Main m;
}

The problem occurs with iPhone SDKs and Mac's own g++ and with versions 4.0.1 and 4.2.
Is there something I've understood incorrectly or is this a bug in g++?

UPDATE
My example contained an accidental bug pointed out Tyler and Martin York, but it isn't the problem here. Below is an updated example.

#include <iostream>
#import <Foundation/Foundation.h>

// An abstract C++ interface
class Foo_cpp {
public:
    virtual void foo() = 0;
};

// Another abstract C++ interface
class Bar_cpp {
public:
    virtual void bar() = 0;
};

// An Objective-C to C++ adaptor. 
// It takes a C++ interface Foo. When it's do_foo method is called it
// delegates call to Foo::foo.
@interface Foo_objc : NSObject {
    Foo_cpp* foo_cpp_;
}
@end

@implementation Foo_objc

- (id)init:(Foo_cpp*)foo {
    self = [super init];
    if (self) {
        foo_cpp_ = foo;
    } 
    return self;
}

- (void) do_foo {
    std::cout << "do_foo: ";
    foo_cpp_->foo();
}
@end 

// Another Objective-C to C++ adaptor. 
@interface Bar_objc : NSObject{
    Bar_cpp* bar_cpp_;
}
@end 

@implementation Bar_objc

- (id)init:(Bar_cpp*)bar {
    self = [super init];
    if (self) {
        bar_cpp_ = bar;
    }
    return self;
}

- (void) do_bar {
    std::cout << "do_bar: ";
    bar_cpp_->bar();
}
@end 

class Main : public Foo_cpp, public Bar_cpp {
    void foo() { 
        std::cout << "foo" << std::endl;
    }
    void bar() {
        std::cout << "bar" << std::endl;
    }
};

int main() {
    Main* m = new Main;    
#if 0 
    // Compiles but produces
    //   do_foo: foo
    //   do_bar: foo !!! incorrect method called !!!
    Foo_objc* fo = [[Foo_objc alloc] init:m];
    Bar_objc* bo = [[Bar_objc alloc] init:m];
#else 
    // Doesn't compile
    Foo_objc* fo = [[Foo_objc alloc] init:(Foo_cpp*)m];
    Bar_objc* bo = [[Bar_objc alloc] init:(Bar_cpp*)m];
    // A line above produces following error
    //    mheritance_test2.mm: In function ‘int main()’:
    //    mheritance_test2.mm:82: error: cannot convert ‘Bar_cpp*’ to ‘Foo_cpp*’ in argument passing
#endif
    [fo do_foo];
    [bo do_bar];
}

UPDATE 2
If init: methods of Fooobjc and Barobjc are renamed to initfoo: and initbar: then it works correctly, but I still can't explain what is the problem with the code. Could it be related how Objective-C creates a method signatures?

Best Answer

I'm editing out my hints on the answering, since I completed the mission ;-)

I am not an Objective-C programmer, but driven by curiosity I couldn't help wondering what's going on and played with the code a bit. What I've found out that the problem surfaces after commenting out everything but Foo* and Bar* parts and adding the following line to the main():

Bar_objc *bo = [[Bar_objc alloc] init:(Bar_cpp*)0];

After playing a bit I figured that it must have something to do with not quite defined result of the alloc message. Which is fixed by splitting the assignment above in two:

Bar_objc *bo = [Bar_objc alloc]; [bo init:(Bar_cpp*)0];

That works just fine. So does casting the alloc results (see the code below). Alternatively, this can be fixed (I believe) with different names for initializers. Maybe also reimplementing alloc. No idea.

The full code with multiple inheritance (it has some other minor changes - I've changed class/public pairs to structs for brevity, removed calling virtuals in constructors, changed delete invocations to dealloc messages, maybe something else):

#include <iostream>
#import <Foundation/Foundation.h>

struct Foo_cpp { virtual void foo() = 0; };
struct Bar_cpp { virtual void bar() = 0; };

@interface Foo_objc : NSObject {
    Foo_cpp* foo_cpp_;
}
- (id)init:(Foo_cpp*)foo;
- (void)do_foo;
@end
@implementation Foo_objc : NSObject {
    Foo_cpp* foo_cpp_;
}
- (id)init:(Foo_cpp*)foo {
    if( self = [super init] ) foo_cpp_ = foo;
    return self;
}
- (void) do_foo { std::cout << "do_foo: "; foo_cpp_->foo(); }
@end 

@interface Bar_objc : NSObject {
    Bar_cpp* bar_cpp_;
}
- (id)init:(Bar_cpp*)bar;
- (void)do_bar;
@end 
@implementation Bar_objc : NSObject {
    Bar_cpp* bar_cpp_;
}
- (id)init:(Bar_cpp*)bar {
    if( self = [super init] ) bar_cpp_ = bar;
    return self;
}
- (void) do_bar { std::cout << "do_bar: "; bar_cpp_->bar(); }
@end 


struct Main : public Foo_cpp, public Bar_cpp {
    Foo_objc* foo_;
    Bar_objc* bar_;

    Main() {
        foo_ = [(Foo_objc*)[Foo_objc alloc] init:this];
        bar_ = [(Bar_objc*)[Bar_objc alloc] init:this];
    }

    ~Main() { [foo_ dealloc]; [bar_ dealloc]; }

    virtual void foo() { std::cout << "foo" << std::endl; }
    virtual void bar() { std::cout << "bar" << std::endl; }
};

int main() {
    Main m;
    [m.foo_ do_foo];
    [m.bar_ do_bar];
}

The result:

do_foo: foo
do_bar: bar

The bottom line: I take it due to somewhat weak typing and possibility to send messages to the objects regardless of types, it's better to have no messages with the same name, but different parameters.

Related Topic