How to avoid undefined behaviour with aligned_storage and polymorphism

Viewed 922

I have some code that basically do this:

struct Base {
    virtual ~Base() = default;
    virtual int forward() = 0;
};

struct Derived : Base {
    int forward() override {
        return 42;
    }
};

typename std::aligned_storage<sizeof(Derived), alignof(Derived)>::type storage;

new (&storage) Derived{};
auto&& base = *reinterpret_cast<Base*>(&storage);

std::cout << base.forward() << std::endl;

I highly doubt it's well defined behaviour. If it's indeed undefined behaviour, how can I fix it? In the code that do the reinterpret_cast, I only know the type of the base class.

On the other hand, if it's well defined behavior in all cases, why is this working and how?

Just keeping a reference to the contained object is not applicable here. In my code I want to apply SBO on a type erased list where the type is created by the user of my library, and basically extends the Base class.

I add elements inside a template function, but in the function that reads it, I cannot know the Derived type. The whole reason why I use a base class is because I only need the forward function in my code that reads it.

Here's what my code looks like:

union Storage {
    // not used in this example, but it is in my code
    void* pointer;

    template<typename T>
    Storage(T t) noexcept : storage{} {
        new (&storage) T{std::move(t)}
    }

    // This will be the only active member for this example
    std::aligned_storage<16, 8> storage = {};
}; 

template<typename Data>
struct Base {
    virtual Data forward();
};

template<typename Data, typename T>
struct Derived : Base<Data> {
    Derived(T inst) noexcept : instance{std::move(inst)} {}

    Data forward() override {
        return instance.forward();
    }

    T instance;
};

template<typename> type_id(){}
using type_id_t = void(*)();

std::unordered_map<type_id_t, Storage> superList;

template<typename T>
void addToList(T type) {
    using Data = decltype(type.forward());

    superList.emplace(type_id<Data>, Derived<Data, T>{std::move(type)});
}

template<typename Data>
auto getForwardResult() -> Data {
    auto it = superList.find(type_id<Data>);
    if (it != superList.end()) {
        // I expect the cast to be valid... how to do it?
        return reinterpret_cast<Base<Data>*>(it->second.storage)->forward();
    }

    return {};
}

// These two function are in very distant parts of code.
void insert() {
    struct A { int forward() { return 1; } };
    struct B { float forward() { return 1.f; } };
    struct C { const char* forward() { return "hello"; } };

   addToList(A{});
   addToList(B{});
   addToList(C{});
}

void print() {
   std::cout << getForwardResult<int>() << std::endl;
   std::cout << getForwardResult<float>() << std::endl;
   std::cout << getForwardResult<const char*>() << std::endl;
}

int main() {
   insert();
   print();
}
3 Answers
Related