The idea that you can substitude derived/base classes for return values/parameters is known as covariant return types and contravariant arguments.
In C++, reference and pointer return types of virtual pointers have covariant overloading in derived types; you can return a more restricted (pointer or reference) to the base return type in derived types.
Contravariance of arguments, where you replace a Derived* argument in base interface with a Base* argument in derived, isn't supported in C++, but you can emulate it with both overloading and overriding.
struct BaseValue {};
struct DerivedValue:BaseValue {};
struct MoreDerivedValue:DerivedValue {};
struct BaseInterface {
virtual void contra_example( DerivedValue* ptr ) = 0;
virtual DerivedValue* co_example() = 0;
virtual ~BaseInterface() {}
};
struct DerivedInterface:BaseInterface {
virtual void contra_example( DerivedValue* ptr ) override final {
contra_example( static_cast<Value*>( ptr ) );
}
void contra_example( Value* ptr ) override = 0;
virtual MoreDerivedValue* co_example() override = 0;
};
co_example is an example of covariance in return type. The compiler does this automatically for us in the case where we are just doing covariance based off of pointers and references into a type heirarchy.
contra_example is an example of contravariance of argument type. ptr, the argument to contra_example, in the case of DerivedInterface can be any Value*. The base interface requires that it be a DerivedValue*.
We can override the base contra_example, then forward to our internal "more accepting" implementation which is an overload in DerivedInterface.
The derived interface is more permissive than the base interface, and it provides guarantees at least as good, or better than the original does.
Now lets get back to your question. First, no, the compiler won't do it for you.
Second, your logic is flawed. Using the Liskov substitution principle, your B must be able to substitude for an A.
But your B has a more restricted contract on its argument to f than A does. A requires an X argument, B requires that it be not only an X but a Y as well.
struct X1{};
struct X2{};
struct Y:X1,X2{};
struct A {
virtual void foo( Y* ) = 0;
virtual ~A() {}
}
struct B1:A {
virtual void foo( Y* ptr ) final override { foo( static_cast<X1*>(ptr) ); }
virtual void foo( X1* ) = 0;
}
struct B2:A {
virtual void foo( Y* ptr ) final override { foo( static_cast<X2*>(ptr) ); }
virtual void foo( X2* ) = 0;
}
this corresponds to your example. The derived interfaces are more permissive of their (implicitly input) argument.
You can jump through hoops to have out-only arguments that support covariance, or simply return them.
virtualmethods, beyond your request for contra variance. It is really really hard to read someone's mind when they cannot express what they want in code. Always try to create a valid compiling example, then do minimal editing of your code to describe what you want to work.