Will you please explain this general rule: “a D is a B” does not imply “a
Container<D> is a Container<B>” with some simple example to be easily understood?
A bus is a vehicle, and a car is a vehicle, but a car is not a bus. A car does not have 12 gears, it doesn't hold 20 passengers, it does not require the same license to drive.
The base class can be called 'vehicle', and 'vehicle' should hold the most basic functions to operate any type of vehicle. Speed is a variable that the 'vehicle' class should maintain, gas increases speed, brakes reduce speed, etc. But the cash earned or the maximum number and count of paying passengers probably should not be maintained by the 'vehicle' class because the 'vehicle' class doesn't know if the inheriting class is a 'bus', a 'taxi', a 'ferrari', or a 'motorcycle', two of which do not generally carry paying passengers.
It seems convenient to store all of your 'vehicle' objects in a vector. You can call any of the 'vehicle' functions on anything in that vector, but it gets interesting if you want to call a 'bus' function on all 'bus' objects in that vector, if you accidentally try to call a 'bus' function like openDoor() on a 'motorcycle' you will run into trouble. Having a separate vector that holds pointers to all those 'bus' objects will start to make more sense. Thus vector <bus *> has different utility from vector <vehicle *> even though a bus is a vehicle.
Polymorphism:
Now, shifting can be declared in the 'vehicle' class, it's common to most 'vehicle' types, but should occur at different times and has different limits per type. If you declare the shift function as virtual, then it can be properly overloaded by inheriting classes, and calling it in the loop will call it from the correct child class type.
#include <iostream>
#include <iterator>
struct base
{
int v = 3 ;
};
struct derived : base
{
double d = 5.6 ;
};
static_assert( sizeof(derived) > sizeof(base) ) ;
int foo( const base* b, std::size_t n )
{
// b[n-1] is implemented in terms of sizeof(base)
// it would be disastrous if the sizeof each element of the array != sizeof(base)
if( b && n > 1 ) return b[n-1].v ;
elsereturn 0 ;
}
template < std::size_t N > int bar( const base (&b)[N] ) { return foo(b) ; }
int main()
{
const derived d[23] ;
foo( d, std::size(d) ) ; // *** unfortunately, this compiles, resulting in general insanity
// note: const derived* can be implicitly converted to const base*
// moral of the story: don't do this
// bar(d) ; // *** error *** : no matching function call (this gives an error, as it should)
// array of derived can't be treated as array of base
}
Thanks for the answers, but still the issue is mystery!
You argue that a derived class/car/bus is a base class/vehicle, but since the vector housing a number of those derived classes and they have distinguished facilities, such a vector is not like a vector of the base class.
It goes without saying that the derived class/bus/car whatever it is, has different facilities in the first place. So how can we say a derived class (D) is a base class (B) but containers of the two aren't alike, while the inconsistencies originally go back to the objects themselves not their containers?
When we say a bus "is a" vehicle, we mean that it has the properties of a vehicle, and the interface of a vehicle. Everything you can do with a vehicle, you can do with a bus.
It can have additional properties and behaviour, unique to buses, but as long as it conforms to the interface of a vehicle, it "is a" vehicle.
In any situation where you have code that operates on a vehicle object, it should be possible to use a bus object, because the bus has the same interface.
This is not true of a vector of buses and a vector of vehicles.
class BusVector {
Bus* p;
};
class VehicleVector {
Vehicle* p;
};
One of them does not inherit the other.
One has busses, the other has vehicles. (HAS-A relationship.)
A Bus might inherit Vehicle, but that does not affect the (non-existent) relationship of the two *Vector classes.
The two classes above end up so similar that we can save effort by making a common template (compile-time polymorphism) and creating them from it:
1 2 3 4 5 6 7
template <typename T>
class Vector {
T* p;
};
using BusVector = Vector<Bus>;
using VehicleVector = Vector<Vehicle>;
We still get two unrelated classes, even though they share some code.
Therefore, generally we can say that two classes, bus and vehicle, are two different types, but since the bus class inherits the vehicle class, we can say that a bus is a vehicle, but vectors/containers of those two classes are different types on their own, and since none of them inherits the other, ergo no relationship and we can't say a vector/container of bus is a vector/container of vehicle. Right?