Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent manually instantiating all template types?

Tags:

c++

templates

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.

like image 404
Benjy Kessler Avatar asked Oct 08 '20 13:10

Benjy Kessler


1 Answers

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.

like image 159
463035818_is_not_a_number Avatar answered Nov 06 '22 05:11

463035818_is_not_a_number