I have a function Foo
that gets two template args L3HdrType
and L4HdrType
. I receive a packet and parse it and then need to call the function. My code currently looks like:
ParsedPacket parsed_packet = parser.Parse(packet);
if (parsed_packet.ip_version() == IPv4 && parsed_packet.ip_proto() == TCP)
Foo(parsed_packet.L3Header<ipv4_hdr>(), parsed_packet.L4Header<tcp_hdr>());
if (parsed_packet.ip_version() == IPv6 && parsed_packet.ip_proto() == TCP)
Foo(parsed_packet.L3Header<ipv6_hdr>(), parsed_packet.L4Header<tcp_hdr>());
if (parsed_packet.ip_version() == IPv4 && parsed_packet.ip_proto() == UDP)
Foo(parsed_packet.L3Header<ipv4_hdr>(), parsed_packet.L4Header<udp_hdr>());
if (parsed_packet.ip_version() == IPv6 && parsed_packet.ip_proto() == UDP)
Foo(parsed_packet.L3Header<ipv6_hdr>(), parsed_packet.L4Header<udp_hdr>());
My question is, is there any way to reduce this code duplication? Something along the lines of:
Foo<parsed_packet.GetL3HeaderType(), parsed_packet.GetL4HeaderType()>(...);
This clearly doesn't work since the given packet's header types aren't known at compile time.
The source of duplication is the fact that two different if-statements are checking IPv4
and mapping it to ipv4_hdr
. If I could in one place in the code specify that IPv4
maps to ipv4_hdr
then It would make the code grow linearly with the number of options and not exponentially since I could write somehow:
if (parsed_packet.ip_version() == IPv4) {
using L3HeaderType = ipv4_hdr;
}
...
Foo<L3HeaderType, L4HeaderType>(...)
Note, my actual code needs to support more than just 4 cases so the code in reality is much uglier than the example here since the number of if-statements grows exponentially with the number of headers.
If inheritance and runtime polymorphism is not an option you can decouple deduction of the two template parameters by doing some acrobatics involving lambdas:
template <typename T> struct foo {};
template <typename T> struct bar {};
// the function to be called
template <typename A, typename B>
void foobar( foo<A> f, bar<B> b) {}
// bind the first parameter
template <typename T>
auto make_foofoo (foo<T> f) {
return [f](auto bar){ foobar(f,bar); };
}
// select second type here
template <typename F>
void do_the_actual_call(F f, int y) {
if (y == 1) f(bar<int>{});
if (y == 2) f(bar<double>{});
}
int main() {
// the "conditions"
int x = 1;
int y = 2;
// select first type here
if (x == 1) {
auto foofoo = make_foofoo(foo<int>{});
do_the_actual_call(foofoo,y);
} else if (x == 2){
auto foofoo = make_foofoo(foo<double>{});
do_the_actual_call(foofoo,y);
}
}
It is still duplicate code, but it scales as x + y
no longer as x * y
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With