vmrob
vmrob

Reputation: 3046

Overload resolution and template functions

I have a template class (OutgoingPacket) with two different functions:

void _prepare() {
    assert(false); // this should have been specialized and the native function never called.
}

template <typename Args, typename ... RemArgs>
void _prepare(Args&& args, RemArgs&& ... remArgs) {
    assert(false); // this should have been specialized and the native function never called.
}

I then define some specializations of the two outside of the class definition:

// no args
template <> void OutgoingPacket<PacketServerAck> ::_prepare();
template <> void OutgoingPacket<PacketSup>       ::_prepare();
template <> void OutgoingPacket<PacketWelcome>   ::_prepare();
template <> void OutgoingPacket<PacketServerPing>::_prepare();

// with args
template <> template <> void OutgoingPacket<PacketGTFO>::_prepare<std::string>(std::string&& message);
template <> template <> void OutgoingPacket<PacketPlayer>::_prepare<std::shared_ptr<User>>(std::shared_ptr<User>&& user);

Function calls to prepare without arguments work as expected, but calls to the overload with arguments call the base template; they trigger the assert.

Why does this happen?


Update: I just tried modifying the specialization definitions to include the reference with the same result:

template <> template <> void OutgoingPacket<PacketGTFO>::_prepare<std::string&&>(std::string&& message);
template <> template <> void OutgoingPacket<PacketPlayer>::_prepare<std::shared_ptr<User>&&>(std::shared_ptr<User>&& user);

On a side note, the reason I'm doing it this way is because I didn't feel that the base class, OutgoingPacket, should be littered with all of these different versions of the prepare function. And I didn't feel that subclassing was appropriate because the differences between different OutgoingPackets would be very small (~4 lines).

Essentially, the OutgoingPacket object is created with arbitrary arguments which it then forwards to the prepare function:

template<typename ... Args>
OutgoingPacket(Args&&... args) {
    _prepare(std::forward<Args>(args)...);
}

If this is bad practice, could I get some advice on design?

Upvotes: 2

Views: 511

Answers (1)

Andy Prowl
Andy Prowl

Reputation: 126542

The reason is most likely that you are invoking your functions with arguments that are not rvalues.

Please notice, that the function parameters of your primary variadic template bind to both rvalues and lvalues: in other words, they are universal references (non-standard term by Scott Meyers), while the function parameters in your specialized template bind only to rvalues.

// The parameters are universal references
template <typename Args, typename ... RemArgs>
void _prepare(Args&& args, RemArgs&& ... remArgs) 

// The parameters are NOT universal references
template <> template <> 
void OutgoingPacket<PacketGTFO>::_prepare<std::string>(std::string&& message);

See this article and this presentation for an explanation of how universal references work. The tricky part is that the && suffix does not always mean rvalue reference: it does not, for instance, in the case of your primary template, but it does in the case of your specialized template.

Thus, if the input arguments of your function are lvalues (e.g. variables with names rather than the return value of a function call), they will bind to your primary template rather than to your specialized template, due to regular overload resolution rules.

Upvotes: 3

Related Questions