Reputation: 279
This makes no sense to me. GCC is complaining that the call below in main()
to processMsg()
is ambiguous, even though all of the template-created processMsg()
calls are reported back as candidates. I've tried implementing this variadic template prototype three different ways and they all lead back to this same issue of ambiguous request. I did get closer when I broke the template implementation up into different cases for and but then I could the compiler could only resolve the first lookup in the tuple.
I've pasted a small example. I'm sure I'm missing something simple....
#include <tuple>
//----------------------------------------------------------------------
//
class MessageBase
{
public:
MessageBase( const int _id ) : m_id( _id ) {}
virtual int getMessageID() const { return( m_id ); }
private:
const int m_id;
};
#define MESSAGE( NAME, VAL ) \
class Message##NAME : public MessageBase { \
public: \
Message##NAME() : MessageBase( VAL ) { } \
};
MESSAGE( One, 1 );
MESSAGE( Two, 2 );
MESSAGE( Ten, 10 );
//----------------------------------------------------------------------
//
template< typename T >
struct MyMessageInterface {
virtual void processMsg( const T& t ) { }
};
template< typename... T >
struct MyMessageHandler : public MyMessageInterface< T >...
{};
template< typename... T >
struct MyMessageHandler< std::tuple< T... > >
: public MyMessageInterface< T >...
{};
//----------------------------------------------------------------------
//
typedef std::tuple< MessageOne, MessageTwo, MessageTen > Foople;
int main()
{
MyMessageHandler< Foople > mmh;
mmh.processMsg( MessageOne() );
}
Upvotes: 4
Views: 99
Reputation: 7904
The problem is that member look-up is ambiguous because all of the MyMessageInterface<T>
s are a direct base class of MyMessageHandler
.
We need to pull in the set of names into MyMessageHandler itself so that we can form the overload set with those names.
First approach might be to do something like: using TMessageInterface<T>::processMsg;...
but of course that's not legal.
My suggestion would be to either do what @Jarod42 did to recursively pull in the processMsg
functions, or you can do:
template <typename... Ts>
struct MyMessageHandler : MyMessageInterface<Ts>... {
template <typename Msg>
void processMsg(const Msg &msg) {
MyMessageInterface<Msg>::processMsg(msg);
}
};
which invokes the specific base class' processMsg
.
Upvotes: 0
Reputation: 56873
You could add a forwarder to the specialization of MyMessageHandler
:
template< typename... T >
struct MyMessageHandler< std::tuple< T... > >
: public MyMessageInterface< T >...
{
template< typename U >
void processMsg( const U& u )
{
MyMessageInterface< U >::processMsg( u );
}
};
The reason you need to do something like this (or what Jarod42 proposed) is that the virtual methods of the base classes are not visible from the derived class when the name is ambiguous. Normally you'd add a using declaration to pull in what you need, but in your case a forwarder might be easier.
Upvotes: 4
Reputation: 7294
You have to (unfortunately) explicitly disambiguate the base class that you want to call:
int main()
{
MyMessageHandler< Foople > mmh;
mmh.MyMessageInterface<MessageOne>::processMsg( MessageOne() );
return 0;
}
You can bury the cast in another template if you like:
template <typename Handler, typename Message>
void caller(Handler &h, const Message &m)
{
h.MyMessageInterface<Message>::processMsg( m );
}
int main()
{
MyMessageHandler< Foople > mmh;
caller(mmh, MessageOne());
return 0;
}
Upvotes: 1
Reputation: 217573
You may rewrite MyMessageHandler
as follow: Live example
template <typename... Ts> struct MyMessageHandler;
template <typename T> struct MyMessageHandler<T>
{
virtual void processMsg(const T&) { }
};
template <typename T, typename...Ts>
struct MyMessageHandler<T, Ts...> : MyMessageHandler<T>, MyMessageHandler<Ts...>
{
using MyMessageHandler<T>::processMsg;
using MyMessageHandler<Ts...>::processMsg;
};
template <typename... Ts>
struct MyMessageHandler<std::tuple<Ts...>> : public MyMessageHandler<Ts...>
{
};
Upvotes: 4