Liskov Substitution Principle, Covariance and Contravariance Explained

The Liskov Substitution Principle (LSP), introduced by Barbara Liskov states that..

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

From this principle comes two other Covariance and Contravariance. What are these in plain english? To satisfy LSP a derived funtion must have covariant return types and contravarient arguments. Covariant means that a derived class's overriding function must have equal or more restrictive return types than the base class's overridden function. Contravariant means that a derived class's overriding function can have equal or less restrictive arguments than the base class's overridden function.

Let's take a look at a simple pseudo code example using sets. I have two classes, class A and derived class B. Now class A has a virtual function called hello which accepts a number 1,2,3 or 4 as an argument and returns a number that is 1,2 or 3.

Now class B that is derived from A, and overrides hello has more restrictive return types (1 or 2) and less restrictive arguments (1,2,3,4 or 5).
 1. A{
 2.     {1,2,3}helo({1,2,3,4})
 4. }
 6. B:A{
 7.     {1,2}helo({1,2,3,4,5})
 9. }
Hide line numbers

It's easy to see how this works. Lets assume (code below) that we are calling the base class hello through a pA pointer. Now to able to substitute derived class B instead of A, you can plainly see that B's hello has to return a number that is greater than one and less than three (equal or more restrictive). If B's hello returns 1 or 2 it's fine, but if it returns 4 then the 'assert' breaks. Also, if B's hello only accepted 1,2 and 3 as arguments then this will break because we're giving it 4. So clearly B's hello has to be able to accept 1,2,3 and 4 as a minimum.
 1. assert( pA->hello(4) >= 1 && pA->hello(1) <= 3)
Hide line numbers