I've made a little library with a generic way to forward and use a variadic that holds exactly one chosen type of each "pack" of types, where the choices are made at run-time.
The library interface:
template <class ...>
struct pack {};
template <class ... Packs, class Functor>
void OneOfEachPackToFunctor (const int choice[], Functor && f);
Example:
You want to use this interface:
template <class X, class Y, class Z>
int Foo (std::string & str);
Possible types for each of Foo's arguments at compile-time:
X: X0, X1, X2, X3, X4, X5, X6, X7, X8
Y: Y0, Y1, Y2
Z: Z0, Z1, Z2, Z3, Z4, Z5
We can decide between these at run-time:
struct FooFunctor
{
// Foo's veriable inputs:
std::string str;
template <class X, class Y, class Z>
void operator () ()
{
Foo<X,Y,Z>(str);
}
};
int main ()
{
using XPack = pack<X0, X1, X2, X3, X4, X5, X6, X7, X8>;
using YPack = pack<Y0, Y1, Y2>;
using ZPack = pack<Z0, Z1, Z2, Z3, Z4, Z5>;
// At some point we choose the arguments:
int choice [] = {3, 1, 5}; // ie. X3, Y1, Z5
FooFunctor ff;
ff.str = "hello";
OneOfEachPackToFunctor<XPack, YPack, ZPack>(choice, ff);
}
Live demo
Specifically for OP:
struct IP
{
const ParsedPacket & pp;
IP(const ParsedPacket & parsed_packet)
: pp(parsed_packet)
{}
template <class Version, class Protocol>
void operator () ()
{
Foo(pp.L3Header<Version>(), pp.L4Header<Protocol>());
}
};
int main ()
{
Packet p;
// packet given at run-time includes versions/protocols:
ParsedPacket pp(p); // (in this example we use a rand() for choices)
using Versions = pack<ipv4_hdr, ipv6_hdr>;
int chosen_version = (pp.ip_version() == IPv6);
using Protocols = pack<tcp_hdr, udp_hdr>;
int chosen_protocol = (pp.ip_proto() == UDP);
int choice[] = {chosen_version, chosen_protocol};
OneOfEachPackToFunctor<Versions, Protocols>(choice, IP(pp));
}
Live demo
Library code:
#include <utility>
template<std::size_t, std::size_t, class>
struct type_if_equal {};
template<std::size_t n, class T>
struct type_if_equal <n, n, T>
{
using type = T;
};
////////////////////////////////////////////////
template<std::size_t, class...>
struct nth_by_multi_inheritance {};
template<std::size_t n, std::size_t ... is, class ... Ts>
struct nth_by_multi_inheritance <n, std::index_sequence<is...>, Ts...>
: type_if_equal<n, is, Ts>...
{};
template<std::size_t n, class ... Ts>
struct nth
: nth_by_multi_inheritance<n, std::index_sequence_for<Ts...>, Ts...>
{};
template<std::size_t n, class ... Ts>
using nth_t = typename nth<n, Ts...>::type;
////////////////////////////////////////////////
template <class...>
struct pack {};
////////////////////////////////////////////////
template <class Functor>
class OneOfEachPack
{
template <class ... Ts>
struct OpenPack
{
// Here is the base case.
// We've "opened" all the packs and now we're
// left only with the types chosen by the user
static void PickType (const int *, Functor & f)
{
f.template operator()<Ts...>();
}
};
template <class ... Ts, class ... PacksThenTs>
struct OpenPack <pack<Ts...>, PacksThenTs...>
{
// We are "opening" the left-most pack, "Pack"
//
// PacksThenTs = [remaining "unopened" packs] then [chosen types from already "opened" packs]
template <int N = 0>
static void PickType (const int * choice, Functor & f)
{
// Each pack has multiple elements (types).
// We consider whether the user has chosen element K
if constexpr (N < sizeof...(Ts))
{
if (N < *choice)
{
PickType<N+1>(choice, f);
}
else // (N == *choice)
{
using ChosenT = nth_t<N, Ts...>;
OpenPack<PacksThenTs..., ChosenT>::PickType(choice + 1, f);
}
}
}
};
public:
template <class ... Packs>
static void ToFunctor (const int choice[], Functor & f)
{
OpenPack<Packs...>::PickType(choice, f);
}
};
template <class ... Packs, class Functor>
void OneOfEachPackToFunctor (const int choice[], Functor && f)
{
OneOfEachPack<Functor>::template ToFunctor<Packs...>(choice, f);
}