First Class Functions in C++
by Malte Skarupke
I’ve been working on a library that allows you to treat functions as first class objects in C++. Meaning you can assign to a function. Here’s a download link, and here’s a very simple example:
#include "fcf.h" void foo() { std::cout << "foo\n"; } void bar() { std::cout << "bar\n"; } void main() { foo(); // prints "foo" auto oldFoo = fcf::function(&foo); FCF_ASSIGN(&foo, &bar); foo(); // prints "bar" FCF_ASSIGN(&foo, oldFoo); foo(); // prints "foo" }
If you have ever worked in a language with first class functions, you’ll know that you can do some quite cool stuff with this.
Here’s a case that used to be very difficult to do in C++:
#include "fcf.h" // ... auto oldPresent = fcf::virtual_function(*device, &IDirect3DDevice9::Present); int x = 10; FCF_VIRTUAL_ASSIGN(*device, &IDirect3DDevice9::Present, [oldPresent, x](IDirect3DDevice9 & device, /* ... */) mutable -> HRESULT { std::cout << "IDirect3DDevice9::Present " << x << std::endl; if (--x <= 0) { FCF_VIRTUAL_ASSIGN(device, &IDirect3DDevice9::Present, oldPresent); } return oldPresent(device, /* ... */); }); // ...
This will override the IDirect3DDevice9::Present method, to call the given lambda function instead. That lambda will run and print for the next ten frames before it resets the Present method back to it’s old functionality.
I say that this used to be very difficult to do, because this is of course just using function hooks, which have been around for a while and with which this is possible, but painful. I think that the user interface is important, and there has never been a good user interface for function hooks. So I provided one.
There are two macros and two methods that you will want to use:
FCF_ASSIGN(function_ptr, callable) makes the function_ptr lead to callable instead. See the first example.
FCF_VIRTUAL_ASSIGN(object_ref, method_ptr, callable) is the same thing for virtual methods. To override virtual methods I need a vtable pointer and for that I need a reference to an object.
fcf::function(function_ptr) will return a callable object that always calls the function as it currently is. Meaning if you assign the function to be something else in the future, the object returned by fcf::function will still make the function behave the old way.
fcf::virtual_function(object, method_ptr) is the same thing for virtual methods.
For non-virtual methods you use the FCF_ASSIGN macro and the fcf::function function. For example:
#include "fcf.h" struct Foo { Foo(int i) : i(i) {} int plusOne() const { return i + 1; } int plusTwo() const { return i + 2; } int plusThree() const { return i + 3; } int i; }; void print(int i) { std::cout << i << std::endl; } void main() { Foo foo(4); print(foo.plusOne()); // prints 5 FCF_ASSIGN(&Foo::plusOne, &Foo::plusTwo); print(foo.plusOne()); // prints 6 auto oldPlusOne = fcf::function(&Foo::plusOne); FCF_ASSIGN(&Foo::plusOne, &Foo::plusThree); print(foo.plusOne()); // prints 7 print(oldPlusOne(foo)); // prints 6 }
I have written the library for Visual Studio 2010 and only tested it with that. I’m messing with some implementation specific details, so expect it to not work on other compilers.
The overhead of this library is pretty tiny. In most cases the overhead of calling a function after it has been assigned to another function is a single jmp instruction. For more complex cases it’s the overhead of a std::function call. For the objects returned by fcf::function and fcf::virtual_function the call overhead is a couple tiny memcpys. The cost of FCF_ASSIGN is one call to VirtualProtect and one memcpy. FCF_VIRTUAL_ASSIGN adds one virtual function call on top of that.
Even though this library is pretty fast and could be used for many general cases, the main use for this library is of course for debugging. You can use this if you just want to know every time that a function in a library is called or if you want to remove and place dynamic data breakpoints before and calling a setter or if you just want to disable a function for a while. There are many ways that this could be useful.
Experiment and tell me your best usages.