Since the Derived class is Base , it should never delay the prerequisites of the base contract : if it should behave like a Base , it should accept BaseInput perfectly. This is known as the Liskov substitution principle.
Although you can check the execution time of your argument, you can never achieve a completely safe type: this compiler can match DerivedInput when it sees a Derived object (static type), but it cannot know which subtype will be behind the object Base ...
Requirements
DerivedX should take DerivedXInputDerivedX::Foo must be equal to the DerivedY::Foo interface
contradict: either the Foo methods are implemented in terms of BaseInput , and therefore have the same interfaces in all derived classes, or the DerivedXInput types DerivedXInput different and they cannot have the same interface.
This, in my opinion, is a problem.
This problem arose when writing closely related classes that are processed in a non-standard form:
class Fruit {}; class FruitTree { virtual Fruit* pick() = 0; }; class FruitEater { virtual void eat( Fruit* ) = 0; }; class Banana : public Fruit {}; class BananaTree { virtual Banana* pick() { return new Banana; } }; class BananaEater : public FruitEater { void eat( Fruit* f ){ assert( dynamic_cast<Banana*>(f)!=0 ); delete f; } };
And framework:
struct FruitPipeLine { FruitTree* tree; FruitEater* eater; void cycle(){ eater->eat( tree->pick() ); } };
Now this proves that the design is too easily broken: there is no part in the design that aligns trees with feeders:
FruitPipeLine pipe = { new BananaTree, new LemonEater };
You can improve design consistency and remove the need for virtual dispatch by creating a template:
template<class F> class Tree { F* pick();
Implementations are indeed specialized specializations:
template<> class Tree<Banana> { Banana* pick(){ return new Banana; } }; ... PipeLine<Banana> pipe;