The template approach as written does have a problem. Consider:
long ip = get_ip();
set_attr<orig_ipv4_src>(ip);
This code does not compile. Even though longs can be converted into unsigned longs, we wind up back in the generic template which presumably contains static_assert(false).
There is a way to fix this:
template<attrt ATTR>
struct attr_info {
typedef void argtype;
};
template<>
struct attr_info<orig_ipv4_src> {
typedef unsigned long argtype;
};
// etc.
template<attrt ATTR>
void set_attr(attr_info<ATTR>::argtype arg) {
// set the attr
}
This will do type promotion nicely. It will also let you get at this information in other ways if you want.
Still, for all its prettiness, I'm not sure this approach has any practical advantages over separate functions. That is, I'm not sure the things this enables are things anyone will ever want to do. And it will take more work for someone new to the project to understand.
I would propose looking through the old c code and seeing if anyone ever invokes this function with a non-literal first parameter. If so, make sure what they're doing is possible in the new system. If not, probably go with separate functions.