Search code examples
c++emulationhigher-order-functionsfunction-compositionnes

Is there a way to create an array of combined functions compile-time in c++?


I'm working on an NES emulator in c++ and figured that the most efficient way to run opcodes would be to call a function pointer in an array of functions that do exactly what the opcode does.

The problem is that each opcode has a specific operation and memory address. While searching for a solution, I stumbled upon lambda expressions. This is definitely good enough for a NES emulator on modern hardware. However, I can't find a solution such that each function in the array contains the machine code for both the operation and the addressing without defining 256 separate functions.

This is along what I had in mind for a similar function that combines f and g:

int addone(int x) {
  return x + 1;
}

int multtwo(int x) {
  return 2 * x;
}

something combine(function<int(int)> f, function <int(int)> g) {
  /* code here */
}

/*
combine(addone, multtwo) creates a function h that has the same machine code as
int h(x) {
  return 2 * x + 1;
}
*/

Any ideas? If I had to take a guess, it would have something to do with templates. Thanks!


Solution

  • I'd say that when you want to write generics for functions that it's kind of a "design pattern" to switch to functors: Compilers are desigined to handle types easily, but handling function pointers for stuff you want to mis-match and keep optimised at compile-time gets ugly!

    So we either write our functions as functors, or we wrap them as functors:

    struct A
    {
        static constexpr int Func (int x)
        {
            return -3*x + 1;
        }
    };
    
    struct B
    {
        static constexpr int Func (int x)
        {
            return -2*x - 5;
        }
    };
    
    // etc...
    

    If we have nice symmetry in how we'll use them, then we can manage them systematically. Eg. if we always want to combine them like f(g(h(...y(z())...))), then we can solve as follows:

    template <class T, class ... Ts>
    struct Combine
    {
        static constexpr int Get ()
        {
            int x = Combine<Ts...>::Get();
            return T::Func(x);
        }
    };
    
    template <class T>
    struct Combine <T> // The base case: the last function in the list
    {
        static constexpr int Get ()
        {
            return T::Func();
        }
    };
    

    demo

    Or if we're in no such luck, we'll have to resort to more old-fashioned inputs like you suggested:

    template <class Funcs, class Data>
    constexpr int Combine (const Data & d) 
    {
        Funcs F;
        // Some use without much symmetry:
        return F.f(F.g(d)) - F.h(d);
    }
    
    int main ()
    {
        struct FuncArgs
        {
            A f;
            B g;
            C h;
        };
    
        return Combine<FuncArgs>(5);
    }
    

    demo

    Note that in the second example I've changed from static methods to non-static. This doesn't really matter - the compiler should optimise these fully regardless, but I think in this case it makes the syntax slightly nicer (and shows an alternative style).