Reputation: 209
I'm not fully comprehending variants' usage, maybe someone can clear up what I'm doing wrong (possibly my approach).
Let the variant object be _Types of two classes, both inheriting the same class.
class base
{
public:
int foo;
};
class a: public base
{
int bar;
};
class b: public base
{
float bar;
};
byte variant_id = 0; // 0 for class A, 1 for class B
std::variant< base, a, b > variant;
Here's how I use the variant:
void func( )
{
auto& _variant = std::get< base >( variant ); // ideally would be = variant_id ? std::get< b >( variant ) : std::get< a >( variant )
_variant.foo = 20;
if ( variant_id == 1 )
{
auto& variant_ = std::get< b >( variant );
variant_.bar = 20.f;
}
else
{
auto& variant_ = std::get< a >( variant );
variant_.bar = 20;
}
Maybe a union is more effective?
union
{
a _a;
b _b;
} variant;
byte variant_id = 0;
Upvotes: 3
Views: 6518
Reputation: 170055
Try not to query the variant for what type it holds. If you do, your code is essentially equivalent to a bunch of dynamic casts in an if chain, a code smell.
Instead, let the variant dispatch for you. And if you want to access the common base
of a
and b
, you do not need a base
member in that variant.
Use a visitor
std::variant< a, b > var;
void func( )
{
std::visit([](auto&& v) {
v.foo = 20; // Both `a` and `b` have a foo, this is well formed.
v.bar = 20; // Both have a `bar` that can be assigned a 20. This is well formed too
}, var);
}
Upvotes: 9
Reputation: 59882
Koehler has given a good answer for some technical errors in your usage, but I feel variant is the wrong tool for the job here.
Typically, you would use an std::variant
for unrelated datatypes. Is there a reason to use variant here? Since you are only holding sub-classes of base
, you'd usually opt for a std::unique_ptr<base>
or std::shared_ptr<base>
(depending on the requirements) and be done with it.
The only reason I'd see to use a variant of sub-classes would be to ensure they can be stored contiguously to reduce memory/indirection costs. And even then, I'd use it through the base class interface, like so:
base& getBase(std::variant<a, b>& v)
{
// the conditional here might be omitted in the generated
// code since the branches might be identical
return v.index() == 0 ? std::get<a>(v) : std::get<b>(v);
}
// use like
base& b = getBase(variant);
b.func(20);
Upvotes: 4
Reputation: 2721
Variant
knows which type it stores. Union
expects you are keeping the track of that type externally. If you try to access the wrong item in a variant
, you get an exception
or nullptr
, for the Union
it is merely undefined behavior.
(ref)
Upvotes: 0
Reputation: 2637
What std::variant
does for you is to track what the current (last assigned) type is, and complain when you try to get a different type. So instead of tracking variant_id
you should be using variant.index()
.
Also I believe that your first extraction of base
will actually fail if the type assigned is not of the base
type. Assuming your objects will always be of type a
or b
(and never base
), you should drop the base
type from the variant type constructor.
I'm assuming here that you are not creating classes base
, a
and b
yourself (and can't touch them), and thus the virtual method approach isn't viable.
Upvotes: 2