What you are asking for is called multiple dispatch, aka multimethods. It isn't a feature of the C++ language.
There are workarounds for special cases, but you can't avoid doing some implementation yourself.
One common pattern for multiple dispatch is called "redispatch", aka "recursive deferred dispatching". Basically, one virtual method resolves one parameters type then calls another virtual method, until all parameters are resolved. The function of the outside (if there is one) just calls the first of these virtual methods.
Assuming there are n derived classes, there will be one method to resolve the first parameter, n to resolve the second, n*n to resolve the third and so on - at worst, anyway. That's a fair amount of manual work, and using conditional blocks based on typeid might be easier for initial development, but it's more robust for maintenance to use redispatch.
class Base;
class Derived1;
class Derived2;
class Base
{
public:
virtual void Handle (Base* p2);
virtual void Handle (Derived1* p1);
virtual void Handle (Derived2* p1);
};
class Derived1 : public Base
{
public:
void Handle (Base* p2);
void Handle (Derived1* p1);
void Handle (Derived2* p1);
};
void Derived1::Handle (Base* p2)
{
p2->Handle (this);
}
void Derived1::Handle (Derived1* p1)
{
// p1 is Derived1*, this (p2) is Derived1*
}
void Derived1::Handle (Derived2* p1)
{
// p1 is Derived2*, this (p2) is Derived1*
}
// etc
Implementing this using a template for the derived classes would be difficult, and the template metaprogramming to handle it would probably be unreadable, unmaintainable and very fragile. Implementing the dispatch using non-template methods, then using a mixin template (a template class that takes its base class as a template parameter) to extend that with additional features may not be so bad, though.
The visitor design pattern is closely related to (basically implemented using) redispatch IIRC.
The other approach is to use a language designed to handle the problem, and there are a few options which work well with C++. One is to use treecc - a domain-specific language for handling AST nodes and multiple-dispatch operations which, like lex and yacc, generates "source code" as output.
All it does to handle the dispatch decisions is generate switch statements based on an AST node ID - which can just as easily be a dynamically-typed value class ID, IYSWIM. However, these are switch statements that you don't have to write or maintain, which is a key difference. The biggest issue I have is that AST nodes have their destructor handling tampered with, meaning that destructors for member data don't get called unless you make a special effort - ie it works best with POD types for fields.
Another option is to use a language preprocessor that supports multimethods. There have been a few of these, partly because Stroustrup did have fairly well developed ideas for supporting multimethods at one point. CMM is one. Doublecpp is another. Yet another is the Frost Project. I believe CMM is closest to what Stroustrup described, but I haven't checked.
Ultimately, though, multiple dispatch is just a way to make a run-time decision, and there are many ways to handle the same decision. Specialist DSLs bring a fair amount of hassle, so you generally only do it if you need a lot of multiple dispatch. Redispatch and the visitor pattern are robust WRT maintenance, but at the expense of some complexity and clutter. Simple conditional statements may be a better choice for simple cases, though beware that detecting the possibility of an unhandled case at compile-time is then difficult if not impossible.
As is often the case, there is no one right way to do it, at least in C++.