Matthew Finlay Matthew Finlay - 22 days ago 18
C++ Question

Upcasting a const vector

Say I have

class Parent
{};

class ChildOne : Parent
{};

class ChildTwo : Parent
{};

void SomeMethod(std::vector<Parent*>& parents);


I understand why I can't pass a
std::vector<ChildOne*>
as an argument to
SomeMethod
as it's possible that
SomeMethod
may be:

void SomeMethod(std::vector<Parent*>& parents)
{
parents.push_back(new ChildTwo);
}


which would be invalid.

But why can't I pass a
std::vector<ChildOne*>
as an argument to

void AnotherMethod(const std::vector<Parent*>& parents);


In this case I can't think of what could go wrong, but I must be missing something.

EDIT:

To clarify, I'm wondering why this restriction exists. In C# (for example), this would compile (with IEnumerables). I assume that there is a failure case that exists.

Answer

You're right that it could work in this particular case and on most common platforms. However, it would be a terrible can of worms to allow this generally.

Consider that the standard doesn't guarantee all data pointers to be of the same size (for example, there are platforms where char* is larger than int*, because they don't have individually addressible bytes). The fact that you can convert a pointer-to-derived into a pointer-to-base does not mean that this conversion is trivial and does not affect the representation in memory. After all, as soon as multiple inheritance enters the picture, even this conversion start to involve changing the value of the pointer.

At the same time, templates are quite opaque in general. We know that a sane implementation of std::vector<T*> will use the same layout for its data for all Ts, as long as T* are the same size. But in general, a template can do anything based on its template arguments, with totally incompatible data layout.

Not to mention specialisation - if the template was specialised for <ChildOne*>, it could be totally incompatible.

Yes, there are cases where a const reference to a particular implementation of a template instantiated with Parent* could be aliased to the same template instantiated with ChildOne*. But these are extremely fragile, and in the general case such substitutability would allow innumerable bugs.


If you know that on your platform with your implementation of the standard library and with your types it's safe, you can create a casting function for it:

template <class D, class S>
const std::vector<D*>& base_cast(const std::vector<S*> &src)
{
  return reinterpret_cast<const std::vector<D*>&>(src);
}

But use at your own risk.

Comments