Cast derived class list to base class list in C++

173 views Asked by At

Is it safe to use reinterpret_cast to convert std::list<Derived *> to std::list<Base *>?

class Base {
    ...
}

class Derived : public Base{
    ...
}

void func(const std::list<Base *> &list);

int main() {
    std::list<Derived *> list1;

    // ...

    func(*reinterpret_cast<std::list<Base *> *>(&list1)); // Safe ?
}

If it's a safe cast, we don't need to copy the list, which may improve performance.

2

There are 2 answers

2
463035818_is_not_an_ai On

This is wrong. Even if you could reinterpret cast a Derived* to a Base*, you cannot reinterpret cast a std::list<Derived*> to a std::list<Base*> in a meaningful, portable way. They are two unrelated types. Being instantiations of the same template does not impose any relation between them, other than being instantiations of the same template.

reinterpret_cast is often misunderstood as an any-to-any cast, but thats not what it is. Actually it has a rather limited set of valid use cases. For a complete list see here: https://en.cppreference.com/w/cpp/language/reinterpret_cast. Casting a std::list<Derived*> to a std::list<Base*> is not among them.


Probably you do not have to copy the list in the first place. If the types are polymorphic then no copy nor cast is needed, you can use the std::list<Base*> and the elements virtual methods. If however you do need to cast a Derived* to a Base* then you can cast the elements via dynamic_cast without casting the whole list. Also no copy needed.

4
Davis Herring On

This code has undefined behavior regardless of the container template: std::list<Base*> and std::list<Derived*> are unrelated types. Even std::vector<int*> and std::vector<const int*> are unrelated.

One reason for this is that allowing these conversions would allow reinterpret_cast<std::list<Base*>&>(derived_list).push_front(base_ptr) to add a pointer of the wrong type. For the same reason, char** cannot be implicitly converted to const char**; unfortunately the const char* const* conversion that is allowed has no analog for container types. The C++ type system just isn’t that powerful.

It’s true that obvious implementations will do what you expect for casts like this because the layouts are all the same, but non-obvious implementations may “misbehave” arbitrarily for code like this. The real answer is to make func accept a range rather than a specific container type, which makes it work with std::list<Derived*>, std::vector<Base*>, and probably std::generator<std::unique_ptr<Base>> as well.