Per Per - 2 months ago 14
C++ Question

Template instantiation ambiguity

I'm playing around with a templated implementation of an FSM and am encountering an ambiguity as follows:

/home/permal/code/FSM/Test/../FSM/dist/include/FSM.h: In instantiation of ‘void fsm::FSM<FSMBaseState>::Event(std::unique_ptr<EventType>) [with EventType = AddEvent; FSMBaseState = EventBaseState]’:
/home/permal/code/FSM/Test/test.cpp:83:44: required from here
/home/permal/code/FSM/Test/../FSM/dist/include/FSM.h:59:4: error: request for member ‘Event’ is ambiguous
myCurrent->Event( std::move( event ) );
^
In file included from /home/permal/code/FSM/Test/../FSM/dist/include/FSM.h:12:0,
from /home/permal/code/FSM/Test/test.cpp:8:
/home/permal/code/FSM/Test/../FSM/dist/include/EventReceiver.h:15:15: note: candidates are: void fsm::EventReceiver<EventType>::Event(std::unique_ptr<_Tp>) [with EventType = SubtractEvent]
virtual void Event( std::unique_ptr<EventType> event ) = 0;
^
/home/permal/code/FSM/Test/../FSM/dist/include/EventReceiver.h:15:15: note: void fsm::EventReceiver<EventType>::Event(std::unique_ptr<_Tp>) [with EventType = AddEvent]
In file included from /home/permal/code/FSM/Test/test.cpp:8:0:
/home/permal/code/FSM/Test/../FSM/dist/include/FSM.h: In instantiation of ‘void fsm::FSM<FSMBaseState>::Event(std::unique_ptr<EventType>) [with EventType = SubtractEvent; FSMBaseState = EventBaseState]’:
/home/permal/code/FSM/Test/test.cpp:91:50: required from here
/home/permal/code/FSM/Test/../FSM/dist/include/FSM.h:59:4: error: request for member ‘Event’ is ambiguous
myCurrent->Event( std::move( event ) );


So my question is why the compiler cannot choose the correct option even though it spells out that there are two possible candidates, one of which is the correct one.

I hope the code below is enough to show the problem, the rest is available on GitHub

This is the class and method where the ambiguity occurs:

template<typename FSMBaseState>
class FSM
{
public:
...

template<typename EventType>
void Event( std::unique_ptr<EventType> event )
{
if( HasState() )
{
// This way of calling causes ambiguous method lookup during template instantiation
myCurrent->Event( std::move( event ) );

// casting to the correct type works, but is it really needed?
// auto* s = myCurrent.get();
// static_cast<EventReceiver<EventType>*>( s )->Event( std::move( event ) );
}
}

};


The above method is called either as
fsm.Event( std::make_unique<AddEvent>() );
or
fsm.Event( std::make_unique<SubractEvent>() );


myCurrent
in the above Event()-method is an instance of the following class:

class EventBaseState
: public fsm::BaseState<EventBaseState>,
public fsm::EventReceiver<AddEvent>,
public fsm::EventReceiver<SubtractEvent>
{
public:
EventBaseState( const std::string& name, fsm::FSM<EventBaseState>& fsm ) :
BaseState( name, fsm )
{}

};


where
EventReceiver
is defined as follows:

template<typename EventType>
class EventReceiver
{
public:
virtual void Event( std::unique_ptr<EventType> event ) = 0;
};


Update



Looking at things from a new angle, I ended up with a pure virtual functions for the
Event<T>(...)
methods, which is actually what I really wanted as the
EventBaseState
class was supposed to be abstract.

class EventBaseState
: public fsm::BaseState<EventBaseState>,
public fsm::EventReceiver<AddEvent>,
public fsm::EventReceiver<SubtractEvent>
{
public:
EventBaseState( const std::string& name, fsm::FSM<EventBaseState>& fsm ) :
BaseState( name, fsm )
{}

virtual void Event( std::unique_ptr<AddEvent> event ) override = 0;
virtual void Event( std::unique_ptr<SubtractEvent> event ) override = 0;
};


Of course, the solution provided by Chajnik-U works too.

Answer

Methods of the base classes do not participate in "cross class" overload when called via derived class instance. You have to place

using EventReceiver<AddEvent>::Event;
using EventReceiver<SubtractEvent>::Event;

inside EventBaseState to force both methods to be visible by overload. For more details from the point of view of the standard (with simplified example) please see here:

Why do multiple-inherited functions with same name but different signatures not get treated as overloaded functions?