Search code examples
c++templatespreprocessor

cPreprocess versus templating in C++ for non-type template


I have a C++ function that does a nested for loop with identical calculations two times, each nested for loop acts on different data, and over slightly different array/loop indexes, but are doing the same thing, conceptually. It would save me a lot of coding/debugging time to just write the first nested for loop once, and then have the compiler duplicate it, recreating the nested loop and swap out the appropriate variables and indexes. Here is a dummy example.

    void function(myStruct &b){
      // first nested for loop
      for(int i=1; i < 10; i++){
        for(int j=0; j < 9; j++){
          b.iF[i,j] = b.someOtherArray[i-1,j]*2;
        }
      }
      // second nested for loop I do not want to code
      for(int i=0; i < 9; i++){
        for(int j=1; j < 10; j++){
          b.jF[i,j] = b.someOtherArray[i,j-1]*2;
        }
      }
    }

The second nested for loop is the same as the first, but "b.iF-->b.jF", "i-1,j-->i,j-1", and so on. Other wise they do the same thing. I have never worked with templates, but this does not seem like a correct use of templates as there is no type difference. And defining macros would involve writing the "meat" of the nested loops as a macro which would not be very readable. Not sure if that is the correct read, or if there is something I am not aware of that would be a good solution.

I could break out each nested loop into a separate function and call them with the correct variables and indexes as arguments, but I want to try something different.

The real use case is going from a 1-dimensional physics problem to a 3-dimensional problem, where I repeat the 1D physics two more times in the other 2 spatial dimension. So it is nice to just write the 1D case, and have the compiler repeat that for the other 2 spatial dimension for me.


Solution

  • I see two ways to do this using higher level abstractions.

    First, the loops themselves can be turned into ranges. Second, the operation could be turned into a procedure.

    void function(myStruct& b){
      auto body = [&](int i0, int j0, int i1, int j1) {
        b.iF[i0,j0] = b.someOtherArray[i1,j1]*2;
      };
    
      auto range1 = range(0, 10);
      auto range2 = range(0, 9);
      (range1 * range2)([&](auto i, auto j){
        body(i,j,i-1,j);
      });
      (range2 * range1)([&](auto i, auto j){
        body(i,j,i,j-1);
      });
    }
    

    Here I have range types that admit operations on them and take function objects. That attempts to remove the structural coding (the for loops, maintaining end pots, etc) and makes it data.

    I rewrite the body so that the input and output arguments are distinct. Then I write a lambda wrapper that makes the decisions. You could take i,j and delta_i and delta_j instead.

        body(i,j,0,-1);
    

    The end goal would be code that compiles down to the same as your hand-written loops.