The following excerpt is from pages 686-687 of Programming: Principles and Practice by Bjarne Stroustrup:
19.3.4 Containers and inheritance
There is one kind of combination of object-oriented programming and generic programming that people always try, but it doesn’t work: attempting to use a container of objects of a derived class as a container of objects of a base class. For example:
vector<Shape> vs; vector<Circle> vc; vs = vc; // error: vector<Shape> required void f(vector<Shape>&); f(vc); // error: vector<Shape> required
But why not? After all, you say, I can convert a Circle to a Shape! Actually, no, you can’t. You can convert a Circle* to a Shape* and a Circle& to a Shape&, but we deliberately disabled assignment of Shapes, so that you wouldn’t have to wonder what would happen if you put a Circle with a radius into a Shape variable that doesn’t have a radius (§14.2.4). What would have happened — had we allowed it — would have been what is called “slicing” and is the class object equivalent of integer truncation (§3.9.2).
I'm not sure I understand why vs = vc
is an error. The annotation says // error: vector<Shape> required
. I tried the following toy example:
#include "../std_lib_facilities.h"
class B {
};
class D : public B {
int x;
public:
D() {
x = 7;
}
};
class D2 : B {
};
// vector<D>& vector<D>::vector<B>() {
// return vector<B>;
// }
int main() {
vector<B> vb;
vector<D> vd;
vb = vd;
return 0;
}
When I attempt to compile it, the error message I get reads:
test.cpp:35:11: error: no match for 'operator=' (operand types are 'Vector' and
Vector<D>
)
35 | vd2 = vd;
| ^~
In file included from test.cpp:1:
../std_lib_facilities.h:70:27: note: candidate: 'Vector& Vector::operator=(const Vector&)'
70 | template< class T> struct Vector : public std::vector
| ^~~~~~
../std_lib_facilities.h:70:27: note: no known conversion for argument 1 from 'Vector' to 'const Vector&'
../std_lib_facilities.h:70:27: note: candidate: 'Vector& Vector::operator=(Vector&&)'
../std_lib_facilities.h:70:27: note: no known conversion for argument 1 from 'Vector' to 'Vector&&'
So, it seems that it's not so much that a vector<Shape>
is required, but rather that vector<Shape>
does not have an assignment operator that takes a vector<Circle>
as input, and vector<Circle>
does not have the conversion function vector<Shape>()
. Is this correct?
This also got me thinking about whether it would be possible to supply a conversion function for the vector<D>
specialisation myself, but when I try to define vector<D>& vector<D>::vector<B>()
, I get the following error:
test.cpp:22:10: error: specializing member 'Vector::operator=' requires 'template<>' syntax 22 | vector& vector::vector() {
| ^
I'm not sure how to write it in template syntax, or if it is even possible to do so. I don't quite see the problem with being able to augment a specialisation with our own member functions. In the last paragraph of the section, Stroustrup says:
Inheritance is a powerful and subtle mechanism and templates do not implicitly extend its reach. [..] Just remember that "
D
is aB
" does not imply "C<D>
is aC<B>
" for an arbitrary templateC
.
So, "D
is a B
" does not imply "C<D>
is a C<B>
", but that doesn't mean that both can't be true at the same time. So, why wasn't I able to define the conversion function?