How can I use modern C++ to get the same effect as
enum Value_type_id { i, f, d };
union Value { int i; float f; double d; };
struct Item { Value_type_id type; Value value; };
Item add(Item lhs, Item rhs)
{
Item result;
switch (lhs.type)
{
case i:
switch (rhs.type)
{
case i:
result.type = i;
result.value.i = lhs.value.i + rhs.value.i;
break;
case f:
result.type = f;
result.value.f = lhs.value.i + rhs.value.f;
break;
case d:
result.type = d;
result.value.d = lhs.value.i + rhs.value.d;
break;
}
break;
case f:
switch (rhs.type)
{
case i:
result.type = f;
result.value.f = lhs.value.f + rhs.value.i;
break;
case f:
result.type = f;
result.value.f = lhs.value.f + rhs.value.f;
break;
case d:
result.type = d;
result.value.d = lhs.value.f + rhs.value.d;
break;
}
break;
case d:
switch (rhs.type)
{
case i:
result.type = d;
result.value.d = lhs.value.d + rhs.value.i;
break;
case f:
result.type = d;
result.value.d = lhs.value.d + rhs.value.f;
break;
case d:
result.type = d;
result.value.d = lhs.value.d + rhs.value.d;
break;
}
break;
}
return result;
}
but without having to tediously code the nested switch statement with nearly identical cases for this and other functions of two instances of Item
? Ideally I'd like to be able to do something like
Item add(Item lhs, Item rhs)
{
return some_magic_here(std::plus{}, lhs, rhs);
}
but I haven't figured out how to implement some_magic_here
.
Basing Item
on std::variant
, and some_magic_here
on std::visit
, seemed appropriate at first glance, until I discovered that std::visit
requires the return value to have the same type for all alternatives represented in the variant. I need return types that are the std::common_type
of the two argument types, which isn't always the same.
Actually, my use case has 8 different data types, which leads to 64 different cases that are identical except for which members of the unions are accessed.
Addition 2023-11-11: My application (an implementation of an interpreted language) is standalone and large (170'000 lines) and uses the enum/union/struct throughout. I can (with a lot of work) replace them with something else, but only wanted to do that if there was clear benefit, such as getting rid of the many near-duplicate switch cases for the different supported data types. The accepted answer (by Miles Budnek) provides that.
Addition 2023-11-15: I now realize that I need a similar solution for vectors, too, but I don't know how to extend the current question's answer to that case, so I've asked a follow-up question here: Refactor nested switch statement on union members with vectors, using modern C++
You are correct that the visitor passed to std::visit
has to return the same type for all cases, but there's no reason that type can't itself be a std::variant
:
using Item = std::variant<int, float, double>;
Item add(Item lhs, Item rhs)
{
return std::visit(
[](auto lhs, auto rhs) { return Item{lhs + rhs}; },
lhs,
rhs
);
}
The return type of the visitor is always std::variant<int, float, double>
, but that variant
holds a different type depending on the type contained in lhs
and rhs
.