c++inheritancepolymorphismunions

Add union of subclasses to vector of base class in C++


I have a base class A, and two subclasses B and C which inherit from A. I would like to be able to store a union of B and C in an std::vector<A*>, but it seems that it may not be possible. Here's a minimal reproducible example of the situation that creates an error:

#include <vector>

struct A {};

struct B : public A {};
struct C : public A {};

union D
{
    B* b;
    C* c;
};

int main(int argc, char** argv)
{
    std::vector<A*> v;

    v.push_back(D());
    v.push_back(new D());

    return 0;
}

In the above example, both push_back lines throw an error. I have also tried removing the * from B and C in the union, and the same lines still throw an error. Am I trying to do something impossible or is there a way to get around this?


Solution

  • In my actual code (not this example) type D is supposed to be able to return an instance of B if asked for an instance of B, or an instance of C if asked for an instance of C ("asked" meaning some other function is traversing the vector and getting objects from it).

    A union is not the right choice for this design. D does not derive from A, so you can't store D objects into a vector of A* pointers.

    Besides, the only reason to store A* pointers in the vector in the first place is if you want to store individual B and C objects together. In which case, you can use dynamic_cast to find them, eg:

    #include <vector>
    
    struct A { virtual ~A() = default; };
    
    struct B : public A {};
    struct C : public A {};
    
    int main(int argc, char** argv)
    {
        std::vector<A*> v;
    
        v.push_back(new B);
        v.push_back(new C);
    
        for(A *a : v)
        {
            if (B *b = dynamic_cast<B*>(a))
            {
                // use b as needed...
            }
            else if (C *c = dynamic_cast<C*>(a))
            {
                // use c as needed...
            }
        }
    
        for(A *a : v)
        {
            delete a;
        }
    
        return 0;
    }
    

    Or, you can use std::variant instead, and not use A* pointers at all, eg:

    #include <vector>
    #include <variant>
    #include <type_traits>
    
    struct A { virtual ~A() = default; };
    
    struct B : public A {};
    struct C : public A {};
    
    int main(int argc, char** argv)
    {
        std::vector<std::variant<B,C>> v;
    
        v.emplace_back(B{});
        v.emplace_back(C{});
    
        for(auto &var : v)
        {
            std::visit([](auto&& arg)
            {
                using T = std::decay_t<decltype(arg)>;
                if constexpr (std::is_same_v<T, B>)
                {
                    // use arg as B as needed...
                }
                else if constexpr (std::is_same_v<T, C>)
                {
                    // use arg as C as needed...
                }
            }, var);
        }
    
        return 0;
    }
    

    Alternatively:

    #include <vector>
    #include <variant>
    
    template<class... Ts>
    struct overloaded : Ts... { using Ts::operator()...; };
    
    template<class... Ts>
    overloaded(Ts...) -> overloaded<Ts...>;
    
    struct A { virtual ~A() = default; };
    
    struct B : public A {};
    struct C : public A {};
    
    int main(int argc, char** argv)
    {
        std::vector<std::variant<B,C>> v;
    
        v.emplace_back(B{});
        v.emplace_back(C{});
    
        for(auto &var : v)
        {
            std::visit(overloaded{
                [](B& arg) {
                    // use arg as needed...
                },
                [](C& arg) {
                    // use arg as needed...
                }
            }, var);
        }
    
        return 0;
    }