Search code examples
c++templatescode-maintainability

Using templates to avoid similar functions


Lets say I have 2 functions which perform the exact same operations on the arguments but use different sets of constants to do so. For an oversimplified example:

int foo1(int x){
    return 3+4*x
}
int foo2(int x){
    return 6-4*x
}

In the real applications assume there will be multiple arguments and constants/literals, and of course the computation will be much more complex. For the sake of simplicity, as well as maintainability, I want to rewrite these two functions as a template that can produce both of these functions, so that I could call foo<1> or foo<2> and the proper function will be generated. I am aware that I could do something like this:

int foo(int x, int funcType){
    const int firstConst = (funcType==1) ? 3 : 6;
    const int secondConst = (funcType==1) ? 4 : -4;
    return firstConst+secondConst*x;
}

but since I am always aware at compile time of which function I want to use, I would like to use templates to avoid the branching. Is there any way to do this?


Solution

  • templatr<int funcType>
    void foo(int x){
      const int firstConst = (funcType==1) ? 3 : 6;
      const int secondConst = (funcType==1) ? 4 : -4;
      return firstConst+secondConst*x;
    }
    

    no compiler worth using under any non-zero optimization setting will have a runtime branch for the above template function.

    And it is often easier to read than traits classes.

    You can have fun with this technique in general, writing long branchy code that compiles down to tight operations. This scales reasonably well if your code decomposes into pieces nicely (like bool do_foo as a template parameter).

    Scaling beyond that you probably want to avoid maintaining a central list of numerical IDs. Finding traits by tag dispatching to a constexpr ADL enabled traits function, or takijg a template non type pointer to constexpr struct, both can give you the zero overhead effect with distributed function sub type declaration.

    Finally, you could just pass a traits class in directly:

    template<class Traits>
    void foo(int x){
      return x*Traits::z+Traits::y;
    }
    

    or

    template<class Traits>
    void foo(int x){
      return x*Traits{}.z+Traits{}.y;
    }
    

    or even

    template<class Traits>
    void foo(int x, Traits traits={}){
      return x*traits.z+traits.y;
    }
    

    depending on specific needs.