Let's assume I have an union type (variant type) like this one (simplified just for demonstration purposes)
enum class Type : uint32_t
{
Unknown = 0,
Int64 = 1,
UInt64 = 2,
Double = 3
};
struct Value
{
Type dtype;
union
{
int64_t i;
uint64_t u;
double d;
};
}
And I intend to dispatch this value to different functions like this (actually function objects, but simplifying again...),
template<typename... T> auto eval(const T&... t)
{
// ...
}
which takes a parameters pack, or simpler ones like:
template<typename T1, typename T2> auto sum(const T1& a, const T2& b)
{
return a+b;
}
In general I need a switch statement with the following structure:
switch (o1.dtype)
{
case Type::Int64:
switch (o2.dtype)
{
case Type::Int64:
F(o1.i, o2.i);
case Type::Double:
F(o2.i, o2.d);
//...
}
break;
case Type::Double
//...
for the case of invoking a functor with 2 parameters. But it gets much worse for the case of 3 parameters...
Is there any way to generalize it via metaprogramming? Ideally I would have only one switch statement, and all the nested ones generated.
You could make a variadic templated function that does the switch
on the first argument and calls itself with the rest and a lambda that saves the first argument's type:
template<typename F, typename... T>
// decltype(auto) is used to deduce the return type from F, throughout
decltype(auto) apply_func(F f, const Value& v, T&&... vs) { // can't say "any number of arguments, all Values", so say "any arguments of any type" and just convert them to Value later
switch (v.dtype) {
case Type::Int64: // bind first argument of f to chosen variant of v, then apply new function (with one less argument) to remaining Values
return apply_func(
[&](const auto&... args) -> decltype(auto) {
return f(v.i, args...);
}, std::forward<T>(vs)...
);
case Type::UInt64:
return apply_func(
[&](const auto&... args) -> decltype(auto) {
return f(v.u, args...);
}, std::forward<T>(vs)...
);
case Type::Double:
return apply_func(
[&](const auto&... args) -> decltype(auto) {
return f(v.d, args...);
}, std::forward<T>(vs)...
);
default: throw std::invalid_argument("Unknown dtype");
}
}
// final overload, when all arguments have been switched on and the chosen variants have been saved into f
template<typename F>
decltype(auto) apply_func(F f) {
return f();
}