12

I'm getting the following compile error in one of my classes, using gcc 3.4.5 (mingw):

src/ModelTester/CModelTesterGui.cpp:1308: error: request for member `addListener' is ambiguous
include/utility/ISource.h:26: error: candidates are: void utility::ISource<T>::addListener(utility::IListener<T>*) [with T = const SConsolePacket&]
include/utility/ISource.h:26: error:                 void utility::ISource<T>::addListener(utility::IListener<T>*) [with T = const SControlPacket&]

Hopefully you can see that ISource<T> is a template interface that just indicates that the object can be an informer for an object that is of some matching type IListener<T>. So the thing that has me irked is this idea that for some reason functions are ambiguous when, as far as I can tell, they are not. The addListener() method is overloaded for different input types IListener<const SConsolePacket&> and IListener<const SControlPacket&>. The usage is:

m_controller->addListener( m_model );

Where m_model is a pointer to an IRigidBody object, and IRigidBody inherits only from IListener< const SControlPacket& > and definately not from IListener< const SConsolePacket& >

As a sanity check, I used doxygen to generate the class hierarchy diagram and doxygen agrees with me that IRigidBody does not derive from IListener< const SConsolePacket& >

Evidently my understanding of inheritence in c++ is not exactly correct. I'm under the impression that IListener<const SControlPacket&> and IListener<const SConsolePacket&> are two different types, and that the function declarations

addListener(IListener<const SConsolePacket&>* listener)

and

addListener(IListener<const SControlPacket&>* listener)

declare two separate functions that do two separate things depending on the (distinct) different type of the parameter that is input. Furthermore, I'm under the impression that a pointer to an IRigidBody is also a pointer to an IListener<const SControlPacket&> and that by calling addListener( m_model ) the compiler should understand that I'm calling the second of the above two functions.

I even tried casting m_model like this:

m_controller->addListener(
        static_cast<IListener<const SControlPacket&>*>(m_model) );

but still get that error. I cannot for the life of me see how these functions are ambiguous. Can anyone shed light on this issue?

P.S. I know how to force the function to be un-ambiguous by doing this:

m_controller->ISource<const SControlPacket&>::addListener( m_model );

I just happen to think that is terribly unreadible and I would prefer not to have to do that.

Edit... just kidding. That apparently doesn't fix the problem as it leads to a linker error:

CModelTesterGui.cpp:1312: undefined reference to `utility::ISource<aerobat::SControlPacket const&>::addListener(utility::IListener<SControlPacket const&>*)'
cheshirekow
  • 4,797
  • 6
  • 43
  • 47
  • What is the relationship between SControlPacket and SConsolePacket? – GRB Aug 21 '09 at 16:59
  • Can you please add why the last line `m_controller->ISource::addListener( m_model );` disambiguates the call? If the functions are overloaded they should be in the same class. Where are those functions declared? – Johannes Schaub - litb Aug 21 '09 at 17:01
  • @GRB There is no relationship. Both are structs the derive from nothing. @litb I was wrong about that. It made it compile but it turns out to lead to a link error when the linker goes to try to find ISource<...>::addListener(...) which is pure virtual. Now I'm very confused. When you say declared I assume you mean defined. They are defined in the concerete classes that derive from IController. – cheshirekow Aug 21 '09 at 17:12
  • @cheshirekow, i mean declared, not defined. Doing "a.B::f" inhibits the virtual call mechanism. Then, a definition for the pure virtual function has to exist if you call it like that. For name-lookup, declarations only are important. A using declaration can stop these ambiguities in that case. – Johannes Schaub - litb Aug 21 '09 at 17:20
  • @litb, Got it. You're spot on. Thanks for the answer as well! – cheshirekow Aug 21 '09 at 17:22

1 Answers1

31

Looks like your situation is like this:

struct A {
  void f();
};

struct B {
  void f(int);
};

struct C : A, B { };

int main() { 
  C c; 
  c.B::f(1); // not ambiguous
  c.f(1);    // ambiguous
}

The second call to f is ambiguous, because in looking up the name, it finds functions in two different base class scopes. In this situation, the lookup is ambiguous - they don't overload each other. A fix would be to use a using declaration for each member name. Lookup will find names in the scope of C and don't lookup further:

struct C : A, B { using A::f; using B::f; };

Now, the call would find two functions, do overload resolution, and find that the one taking int will fit. Carried over to your code, it would mean that you have to do something like the following

struct controller : ISource<const SConsolePacket&>, ISource<const SControlPacket&> {
  using ISource<const SConsolePacket&>::addListener;
  using ISource<const SControlPacket&>::addListener;
};

Now, the two names are in the same scope, and now they can overload each other. Lookup will now stop at the controller class, not diving further into the two base-class branches.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • NICE! So thats what those things are for. I don't think I ever really understood using declarations (a consequence of not really understanding name lookup). Thank you very much. As a note, I actually put the using declaration in the IController interface. I think that is the appropriate place for it. – cheshirekow Aug 21 '09 at 17:21
  • At least in g++, two usings are not allowed. You either choose A::f or B::f in the derived class C. – Dingle May 24 '11 at 03:14
  • @Dingle prolly an old version of g++ - fwiw (for recent users), 4.7.2 handles two usings fine – kfmfe04 Mar 20 '13 at 11:19
  • 2
    Well, a little clarification, what should I do in case of multiple variadic inheritance? – Nevermore Apr 01 '16 at 09:43
  • So why does the standard require the compiler to throw up its arms instead of doing overload resolution in the original case? – LB-- Aug 31 '16 at 13:46
  • @lb- i guess that's what they thought would be the best compromise in terms if consistency, usability and safety. If you have nested namespaces and in the inner namespace declare a function with the same name as in the outer namespace, they also won't overload (consistency). And imagine if you add a function to a base class whose name matches that of a derived class by chance. May break code that uses the derived class (safety) if the functions overload. Those are guesses only, though. – Johannes Schaub - litb Aug 31 '16 at 14:55