Search code examples
c++castingstlunordered-set

How do you static_cast an std::unordered_set?


I'm trying to static_cast a unordered_set and I want to know if this is possible without undefined behavior.

Here is what I am trying to accomplish:

#include <unordered_set>

struct  Base{};
struct Derived : public Base{ Derived() = default; };

int main(void){
    std::unordered_set<Base *> set;
    set.insert(new Derived{});
    auto set_ptr{static_cast<std::unordered_set<Derived *>*>(&set)};
}

I'm trying to static_cast a set of Base * into a set of Derived *.

However this will not compile with an error:

main.cpp: In function ‘int main()’:
main.cpp:21:66: error: invalid static_cast from type ‘std::unordered_set*’ to type ‘std::unordered_set*’
     auto set_ptr{static_cast<std::unordered_set<Derived *>*>(&set)};

I was wondering if there is a way to do this without entering undefined behavior territory.


Solution

  • In most cases you cannot perform casts with respect to the template argument, such as

    MyTemplateClass<T> foo;
    MyTemplateClass<U>& bar = std::static_cast<MyTemplateClass<U>&>(foo);
    

    because types MyTemplateClass<U> and MyTemplateClass<T> are completely unrelated in terms of inheritance structure. One of the two could even be specialized to be a completely different thing!

    In your case MyTemplateClass is std::unordered_set.

    In case of a container of pointers, such as std::set<T*> we have a bit more knowledge of what is contained: pointers, and there are few things that could be made:

    • Ugly. Nonstandard. Dangerous. By standard it's undefined behavior.

      Just do a reinterpret_cast<std::unorederd_set<Derived*>&>. It will work in most cases. But it is now entirely your responsibility not to break things. For example, you must make sure that there is no Base* element in the set when the std::unorederd_set<Derived*> reference is being used. It will be very easy to forget when you pass it to a function or store as a field of some object.

    • Clean but boilerplaty.

      Write an adapter. Your own implementation of std::unordered_set<Derived*> which holds a reference to std::unordered_set<Base*> underneath and performs all the necessary casts on the way, each time you access an element.

      For example, you will most likely need to write your own iterators over it. The accessor operator* will perform the static_cast or dynamic_cast to access its element. Or better yet, have the iterator stop only on elements that are in fact Derived* and skipping over all other Base*.