You are asking a question and providing a code example that fails but for a different reason. From the wording of your question:
Why are references/pointers required for polymorphism?
struct base {
virtual void f();
};
struct derived : public base {
virtual void f();
};
void call1( base b ) {
b.f(); // base::f
}
void call2( base &b ) {
b.f(); // derived::f
}
int main() {
derived d;
call1(d);
call2(d);
}
When you use pass-by-value semantics (or store derived elements in a base container) you are creating copies of type base
of the elements of type derived
. That is called slicing, as it resembles the fact that you have a derived
object and you slice/cut only the base
subobject from it. In the example, call1
does not work from the object d
in main, but rather with a temporary of type base
, and base::f
is called.
In the call2
method you are passing a reference to a base
object. When the compiler sees call2(d)
in main it will create a reference to the base
subobject in d
and pass it to the function. The function performs the operation on a reference of type base
that points to an object of type derived
, and will call derived::f
. The same happens with pointers, when you get a base *
into a derived
object, the object is still derived
.
Why can I not pass a container of derived
pointers to a function taking a container of base
pointers?
_Clearly if derived
are base
, a container of derived
is a container of base
.
No. Containers of derived
are not containers of base
. That would break the type system. The simplest example of using a container of derived
as container of base
objects breaking the type system is below.
void f( std::vector<base*> & v )
{
v.push_back( new base );
v.push_back( new another_derived );
}
int main() {
std::vector<derived*> v;
f( v ); // error!!!
}
If the line marked with error was allowed by the language, then it would allow the application to insert elements that are not of type derived*
into the container, and that would mean lots of trouble...
But the question was about containers of value types...
When you have containers of value types, the elements get copied into the container. Inserting an element of type derived
into a container of type base
will make a copy of the subobject of type base
within the derived
object. That is the same slicing than above. Besides that being a language restriction, it is there for a good reason, when you have a container of base
objects, you have space to hold just base
elements. You cannot store bigger objects into the same container. Else the compiler would not even know how much space to reserve for each element (what if we later extend with an even-bigger type?).
In other languages it may seem as this is actually allowed (Java), but it is not. The only change is in the syntax. When you have String array[]
in Java you are actually writting the equivalent of string *array[]
in C++. All non-primitive types are references in the language, and the fact that you do not add the *
in the syntax does not mean that the container holds instances of String, containers hold references into Strings, that are more related to c++ pointers than c++ references.