Skip to content

Instantly share code, notes, and snippets.

@twoscomplement
Last active August 19, 2023 08:32
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save twoscomplement/030818a6c38c5a983482dc3a385a3ab8 to your computer and use it in GitHub Desktop.
Save twoscomplement/030818a6c38c5a983482dc3a385a3ab8 to your computer and use it in GitHub Desktop.
TransientFunction: A light-weight alternative to std::function [C++11]
// TransientFuction: A light-weight alternative to std::function [C++11]
// Pass any callback - including capturing lambdas - cheaply and quickly as a
// function argument
//
// Based on:
// https://deplinenoise.wordpress.com/2014/02/23/using-c11-capturing-lambdas-w-vanilla-c-api-functions/
//
// - No instantiation of called function at each call site
// - Simple to use - use TransientFunction<> as the function argument
// - Low cost: cheap setup, one indirect function call to invoke
// - No risk of dynamic allocation (unlike std::function)
//
// - Not C-friendly, unlike the original Reverse - but easy to adapt
// - NOT PERSISTENT: Use for synchronous calls only
// - No thought has been given to argument forwarding in this implementation
//
// Compiles as C++11 using recent g++, clang, and msvc compilers
// Try it at https://godbolt.org
//
// Prior art:
// A proposal to the C++ ISO committee has been written by Vittorio Romeo,
// which lists a number of other instances of this same pattern:
// function_ref: a non-owning reference to a Callable
// https://vittorioromeo.info/Misc/p0792r1.html
// https://www.youtube.com/watch?v=6EQRoqELeWc
// https://vittorioromeo.info/index/blog/passing_functions_to_functions.html
//
#pragma once
// For std::decay
#include <type_traits>
template<typename>
struct TransientFunction; // intentionally not defined
template<typename R, typename ...Args>
struct TransientFunction<R(Args...)>
{
using Dispatcher = R(*)(void*, Args...);
Dispatcher m_Dispatcher; // A pointer to the static function that will call the
// wrapped invokable object
void* m_Target; // A pointer to the invokable object
// Dispatch() is instantiated by the TransientFunction constructor,
// which will store a pointer to the function in m_Dispatcher.
template<typename S>
static R Dispatch(void* target, Args... args)
{
return (*(S*)target)(args...);
}
template<typename T>
TransientFunction(T&& target)
: m_Dispatcher(&Dispatch<typename std::decay<T>::type>)
, m_Target(&target)
{
}
// Specialize for reference-to-function, to ensure that a valid pointer is
// stored.
using TargetFunctionRef = R(Args...);
TransientFunction(TargetFunctionRef target)
: m_Dispatcher(Dispatch<TargetFunctionRef>)
{
static_assert(sizeof(void*) == sizeof target,
"It will not be possible to pass functions by reference on this platform. "
"Please use explicit function pointers i.e. foo(target) -> foo(&target)");
m_Target = (void*)target;
}
R operator()(Args... args) const
{
return m_Dispatcher(m_Target, args...);
}
};
#if defined(TRANSIENTFUNCTION_DEMO)
// Example usage:
void vv(TransientFunction<void()>);
void vi(TransientFunction<void(int)>);
int iiii(TransientFunction<int(int, int, int)>);
static void g() {}
int demonstration()
{
// Non-capturing lambda, void()
vv([]{});
// Capturing lambda, void()
int x = 3;
vv([&]{x++;}); // x == 4
// Free function, void()
// both pointer to function and reference to function are valid
vv(&g);
vv(g);
// std::function, void(); commented out because it wrecks codegen
// std::function<void()> f = [&]{ x += 21; };
// vv(f);
vi([&](int i){x += i;}); // x == 11
// x == 11 + 3 + 4 * 5 == 34
return iiii([&](int a, int b, int c) { return x + a + b * c; });
}
/*
By defining vv(), vi() and iiii() as follows, clang and gcc are able to inline
and simplify all code in demonstration(). Mark those functions static for even
less generated code.
demonstration():
mov eax, 34
ret
*/
void vv(TransientFunction<void()> f)
{
f();
}
void vi(TransientFunction<void(int)> f)
{
f(7);
}
int iiii(TransientFunction<int(int, int, int)> f)
{
return f(3, 4, 5);
}
#endif
#if 0
#include <stdint.h>
#include <stdlib.h>
class MemAllocStack;
// For a more "real-world" perspective, we can look at the examples from
// https://deplinenoise.wordpress.com/2014/02/23/using-c11-capturing-lambdas-w-vanilla-c-api-functions/
// QuickSort without TransientFunction:
typedef int64_t (*QuickCompareFunc)(const void*, const void*);
typedef int64_t (*QuickCompareFuncReentrant)(const void*, const void*, void* arg);
void QuickSort (void* __restrict elems, int64_t elem_count, size_t elem_size,
QuickCompareFunc cf);
void QuickSort (void* __restrict elems, int64_t elem_count, size_t elem_size,
QuickCompareFuncReentrant cf, void* arg);
template <typename T, typename Func>
void QuickSort(T* __restrict elems, int64_t elem_count, Func f)
{
auto compare_closure = [](const void* ll, const void* rr,
void* context) -> int64_t {
const T* l = (const T*) ll;
const T* r = (const T*) rr;
return (*(Func*)context)(l, r);
};
QuickSort(elems, elem_count, sizeof elems[0], compare_closure, &f);
}
// QuickSort with TransientFunction
// Use TransientFunction in comparison function typedef
using QuickCompareTFunc = TransientFunction<int64_t (const void*, const void*)>;
// Only one external function required - no overload required for reentrant
// comparison function: pass any closure
void QuickSortTF(void* __restrict elems, int64_t elem_count, size_t elem_size,
QuickCompareTFunc cf);
template <typename T, typename Func>
void QuickSortTF(T* __restrict elems, int64_t elem_count, Func f)
{
// capture function rather than pass as arg
auto compare_closure = [&f](const void* ll, const void* rr) -> int64_t {
const T* l = (const T*) ll;
const T* r = (const T*) rr;
return f(l, r); // simpler dispatch
};
// Pass the closure directly
QuickSortTF(elems, elem_count, sizeof elems[0], compare_closure);
}
// DecompressFile
// Without TransientFunction:
typedef bool (ZReadFn)(void* target_buffer, size_t read_size, void* user_data);
bool DecompressFile(void* buffer, size_t compressed_size, size_t decompressed_size,
MemAllocStack* scratch, void *user_data, ZReadFn* read_fn);
template <typename Fn>
bool DecompressFile(void* buffer, size_t cs, size_t ds,
MemAllocStack* scratch, Fn fn)
{
auto closure = [](void* target_buffer, size_t read_size, void* user_data) -> bool
{
return (*(Fn*)user_data)(target_buffer, read_size);
};
return DecompressFile(buffer, cs, ds, scratch, &fn, closure);
}
// Using TransientFunction:
using ZReafTFn = TransientFunction<bool (void* target_buffer, size_t read_size)>;
bool DecompressFile(void* buffer, size_t compressed_size, size_t decompressed_size,
MemAllocStack* scratch, ZReadFn read_fn);
// That's it. No forwarding template required.
#endif
/*
Copyright (c) 2018 Jonathan Adamczewski
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
@Perre67
Copy link

Perre67 commented Sep 1, 2021

I really like this approach as is gives much less overhead that std::function (great job!).
However, I have some major problems using it when using it inside classes.
When I use TransientFunction (instead of std:.function), it seems that the "this" pointer
get corrupted when it returns from TransientFunction functionality.

The error occur inside this function (target seems to be wrong?):
template<typename S> static R Dispatch(void* target, Args... args) { return (*(S*)target)(args...); }

I'm using a clang compiler, and my test-code:
class Z { int TestFunc(int V) { return (V); } TransientFunction<int(int)> Fn = TestFunc; public: int Test(int V) { return (Fn(V)); } }; int TestZ() { Z theZ; int V=theZ.Test(1); } int _TestZ=TestZ();

Have you tested something like this yourself?

@twoscomplement
Copy link
Author

@Perre67 Your test code doesn't compile for me: https://godbolt.org/z/W5Mr9Wv11 - maybe I've misunderstood something?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment