I've got a real situation which can be summarized in the following example:
template< typename ListenerType >
struct Notifier
{
void add_listener( ListenerType& ){}
};
struct TimeListener{ };
struct SpaceListener{ };
struct A : public Notifier< TimeListener >
, public Notifier< SpaceListener >
{
};
struct B : TimeListener{ };
int main()
{
A a;
B b;
a.add_listener( b ); // why is ambiguous?
return 0;
}
Why is not obvious to the compiler that B
is a TimeListener
, and therefore the only possible overload resolution is Notifier< TimeListener >::add_listener( TimeListener& )
?
The lookup rules for member names say that your code is ambiguous, because the name is found in two base classes and therefore the lookup set is invalid. You don't need to be familiar with all the details of lookup sets and merging; the important detail is that both base classes are checked and the name add_listener
is found in both, which creates an ambiguity.
The easy fix is to bring those base class names into A
with using-declarations. This means that both versions of add_listener
are looked up in A
, rather than in the base classes, so there is no merge ambiguity:
struct A : public Notifier< TimeListener >
, public Notifier< SpaceListener >
{
using Notifier<TimeListener>::add_listener;
using Notifier<SpaceListener>::add_listener;
//plus any more base classes
};