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?
In my actual code (not this example) type
D
is supposed to be able to return an instance ofB
if asked for an instance ofB
, or an instance ofC
if asked for an instance ofC
("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;
}