Skip to content

Instantly share code, notes, and snippets.

@vittorioromeo
Last active August 24, 2023 08:15
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save vittorioromeo/6462221 to your computer and use it in GitHub Desktop.
Save vittorioromeo/6462221 to your computer and use it in GitHub Desktop.
Don Clugston's fast delegate C++11 implementation
#ifndef SSVU_FASTFUNC
#define SSVU_FASTFUNC
#include <cstring>
#include <type_traits>
#include <cassert>
#include <cstddef>
#include <memory>
#include <new>
#include <utility>
namespace ssvu
{
namespace Internal
{
class AnyClass;
using AnyPtrThis = AnyClass*;
using AnyPtrFunc = void(AnyClass::*)();
template<typename TReturn = void, typename... TArgs> using AnyPtrFuncT = TReturn(AnyClass::*)(TArgs...);
template<typename TReturn = void, typename... TArgs> using AnyPtrStaticFuncT = TReturn(*)(TArgs...);
constexpr std::size_t SingleMemFuncPtrSize{sizeof(void(AnyClass::*)())};
template<class TOut, class TIn> union HorribleUnion { TOut out; TIn in; };
template<class TOut, class TIn> inline TOut horrible_cast(TIn mIn) noexcept { HorribleUnion<TOut, TIn> u; static_assert(sizeof(TIn) == sizeof(u) && sizeof(TIn) == sizeof(TOut), "Cannot use horrible_cast<>"); u.in = mIn; return u.out; }
template<class TOut, class TIn> inline TOut unsafe_horrible_cast(TIn mIn) noexcept { HorribleUnion<TOut, TIn> u; u.in = mIn; return u.out; }
template<std::size_t TN> struct SimplifyMemFunc
{
template<class TThis, class TFunc> inline static AnyPtrThis convert(const TThis*, TFunc, AnyPtrFunc&) noexcept
{
static_assert(TN - 100, "Unsupported member function pointer on this compiler");
return 0;
}
};
template<> struct SimplifyMemFunc<SingleMemFuncPtrSize>
{
template<class TThis, class TFunc> inline static AnyPtrThis convert(const TThis* mThis, TFunc mFunc, AnyPtrFunc& mFuncOut) noexcept
{
mFuncOut = reinterpret_cast<AnyPtrFunc>(mFunc);
return reinterpret_cast<AnyPtrThis>(const_cast<TThis*>(mThis));
}
};
template<typename TReturn, typename... TArgs> struct Closure
{
private:
using PtrFuncT = AnyPtrFuncT<TReturn, TArgs...>;
using PtrStaticFuncT = AnyPtrStaticFuncT<TReturn, TArgs...>;
AnyPtrThis ptrThis{nullptr};
AnyPtrFunc ptrFunction{nullptr};
public:
template<class TThis, class TFunc> inline void bind(TThis* mThis, TFunc mFunc) noexcept { ptrThis = SimplifyMemFunc<sizeof(mFunc)>::convert(mThis, mFunc, ptrFunction); }
template<class TThis, class TInvoker> inline void bind(TThis* mThis, TInvoker mInvoker, PtrStaticFuncT mFunc) noexcept
{
if(mFunc == nullptr) ptrFunction = nullptr; else bind(mThis, mInvoker);
ptrThis = horrible_cast<AnyPtrThis>(mFunc);
}
inline bool operator==(std::nullptr_t) const noexcept { return ptrThis == nullptr && ptrFunction == nullptr; }
inline bool operator==(const Closure& mRhs) const noexcept { return ptrThis == mRhs.ptrThis && ptrFunction == mRhs.ptrFunction; }
inline bool operator==(PtrStaticFuncT mPtr) const noexcept { return mPtr == nullptr ? *this == nullptr : mPtr == reinterpret_cast<PtrStaticFuncT>(getStaticFunc()); }
inline bool operator!=(std::nullptr_t) const noexcept { return !operator==(nullptr); }
inline bool operator!=(const Closure& mRhs) const noexcept { return !operator==(mRhs); }
inline bool operator!=(PtrStaticFuncT mPtr) const noexcept { return !operator==(mPtr); }
inline bool operator<(const Closure& mRhs) const { return ptrThis != mRhs.ptrThis ? ptrThis < mRhs.ptrThis : std::memcmp(&ptrFunction, &mRhs.ptrFunction, sizeof(ptrFunction)) < 0; }
inline bool operator>(const Closure& mRhs) const { return !operator<(mRhs); }
inline std::size_t getHash() const noexcept { return reinterpret_cast<std::size_t>(ptrThis) ^ Internal::unsafe_horrible_cast<std::size_t>(ptrFunction); }
inline AnyPtrThis getPtrThis() const noexcept { return ptrThis; }
inline PtrFuncT getPtrFunction() const noexcept { return reinterpret_cast<PtrFuncT>(ptrFunction); }
inline PtrStaticFuncT getStaticFunc() const noexcept { return horrible_cast<PtrStaticFuncT>(this); }
};
template<typename TReturn, typename... TArgs> class FastFuncImpl
{
private:
using PtrStaticFuncT = AnyPtrStaticFuncT<TReturn, TArgs...>;
Closure<TReturn, TArgs...> closure;
inline TReturn invokeStaticFunc(TArgs... mArgs) const { return (*(closure.getStaticFunc()))(std::forward<TArgs>(mArgs)...); }
protected:
template<class TThis, class TFunc> inline void bind(TThis* mThis, TFunc mFunc) noexcept { closure.bind(mThis, mFunc); }
template<class TFunc> inline void bind(TFunc mFunc) noexcept { closure.bind(this, &FastFuncImpl::invokeStaticFunc, mFunc); }
public:
inline FastFuncImpl() noexcept = default;
inline FastFuncImpl(std::nullptr_t) noexcept { }
inline FastFuncImpl(PtrStaticFuncT mFunc) noexcept { bind(mFunc); }
template<typename X, typename Y> inline FastFuncImpl(X* mThis, Y mFunc) noexcept { bind(mThis, mFunc); }
inline FastFuncImpl& operator=(PtrStaticFuncT mFunc) noexcept { bind(mFunc); }
inline TReturn operator()(TArgs... mArgs) const { return (closure.getPtrThis()->*(closure.getPtrFunction()))(std::forward<TArgs>(mArgs)...); }
inline bool operator==(std::nullptr_t) const noexcept { return closure == nullptr; }
inline bool operator==(const FastFuncImpl& mImpl) const noexcept { return closure == mImpl.closure; }
inline bool operator==(PtrStaticFuncT mFuncPtr) const noexcept { return closure == mFuncPtr; }
inline bool operator!=(std::nullptr_t) const noexcept { return !operator==(nullptr); }
inline bool operator!=(const FastFuncImpl& mImpl) const noexcept { return !operator==(mImpl); }
inline bool operator!=(PtrStaticFuncT mFuncPtr) const noexcept { return !operator==(mFuncPtr); }
inline bool operator<(const FastFuncImpl& mImpl) const { return closure < mImpl.closure; }
inline bool operator>(const FastFuncImpl& mImpl) const { return !operator<(mImpl); }
};
}
template<typename T> struct MemFuncToFunc;
template<typename TReturn, typename TThis, typename... TArgs> struct MemFuncToFunc<TReturn(TThis::*)(TArgs...) const> { using Type = TReturn(*)(TArgs...); };
#define ENABLE_IF_CONV_TO_FUN_PTR(x) typename std::enable_if<std::is_constructible<typename MemFuncToFunc<decltype(&std::decay<x>::type::operator())>::Type, x>::value>::type* = nullptr
#define ENABLE_IF_NOT_CONV_TO_FUN_PTR(x) typename std::enable_if<!std::is_constructible<typename MemFuncToFunc<decltype(&std::decay<x>::type::operator())>::Type, x>::value>::type* = nullptr
#define ENABLE_IF_SAME_TYPE(x, y) typename = typename std::enable_if<!std::is_same<x, typename std::decay<y>::type>{}>::type
template<typename T> class FastFunc;
template<typename TReturn, typename... TArgs> class FastFunc<TReturn(TArgs...)> : public Internal::FastFuncImpl<TReturn, TArgs...>
{
private:
using BaseType = Internal::FastFuncImpl<TReturn, TArgs...>;
std::shared_ptr<void> storage;
template<typename T> inline static void funcDeleter(void* mPtr) { static_cast<T*>(mPtr)->~T(); operator delete(mPtr); }
public:
using BaseType::BaseType;
inline FastFunc() noexcept = default;
template<typename TFunc, ENABLE_IF_SAME_TYPE(FastFunc, TFunc)> inline FastFunc(TFunc&& mFunc, ENABLE_IF_CONV_TO_FUN_PTR(TFunc))
{
using FuncType = typename std::decay<TFunc>::type;
this->bind(&mFunc, &FuncType::operator());
}
template<typename TFunc, ENABLE_IF_SAME_TYPE(FastFunc, TFunc)> inline FastFunc(TFunc&& mFunc, ENABLE_IF_NOT_CONV_TO_FUN_PTR(TFunc))
: storage(operator new(sizeof(TFunc)), funcDeleter<typename std::decay<TFunc>::type>)
{
using FuncType = typename std::decay<TFunc>::type;
new (storage.get()) FuncType(std::forward<TFunc>(mFunc));
this->bind(storage.get(), &FuncType::operator());
}
};
#undef ENABLE_IF_CONV_TO_FUN_PTR
#undef ENABLE_IF_NOT_CONV_TO_FUN_PTR
#undef ENABLE_IF_SAME_TYPE
}
#endif
@vittorioromeo
Copy link
Author

Example benchmark code:

int tempGlobalVar{0};
bool tempGlobalState{false};
int rawFunc(int& xx, int i) { xx+=i; tempGlobalState = !tempGlobalState; return i + 1; }
struct TestStruct { void rawMemFunc(int& xx) const { tempGlobalState = !tempGlobalState; xx += 10; } };
constexpr int numberOfTests{1};
constexpr int repeats{500000000};

int main()
{
    string tempStr{"hello from testequal (capture)"};
    FastFunc<void()> testEqual = nullptr;
    FastFunc<void()> testEqual2 = nullptr;
    if(testEqual == nullptr) testEqual = []{ lo << "hello from testequal (trivial)" << endl; };
    testEqual();
    if(testEqual != nullptr) testEqual = [&tempStr]{ lo << tempStr << endl; };
    testEqual(); testEqual = nullptr;
    if(testEqual == nullptr) testEqual = []{ lo << "hello from testequal (trivial)" << endl; };
    testEqual();
    if(testEqual != nullptr) testEqual = [&tempStr]{ lo << tempStr << endl; };
    testEqual();
    testEqual = testEqual2;
    testEqual2 = [&tempStr]{ lo << tempStr << endl; };
    testEqual = testEqual2;
    testEqual();
    testEqual2();
    testEqual2 = [&tempStr]{ lo << "new" + tempStr << endl; };
    testEqual = testEqual2;
    testEqual();
    testEqual2();


    int tempVar{0};
    TestStruct testStr;
    for(int nTest = 0; nTest < numberOfTests; ++nTest)
    {
        {
            startBenchmark(); { for(int i = 0; i < repeats; ++i) rawFunc(tempVar, i); } lo << lt("raw func") << endBenchmark() << endl;
        }
        {
            function<int(int&, int)> t1 = &rawFunc;
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(tempVar, i); } lo << lt("raw func std::func") << endBenchmark() << endl;
        }
        {
            FastFunc<int(int&, int)> t1 = &rawFunc;
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(tempVar, i); } lo << lt("raw func don_delegate") << endBenchmark() << endl;
        }
        {
            delegate<int(int&, int)> t1 = &rawFunc;
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(tempVar, i); } lo << lt("raw func staticdelegate") << endBenchmark() << endl;
        } lo << endl;

        {
            startBenchmark(); { for(int i = 0; i < repeats; ++i) testStr.rawMemFunc(tempVar); } lo << lt("raw memfunc") << endBenchmark() << endl;
        }
        {
            function<void()> t1 = [&]{ testStr.rawMemFunc(tempVar); };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(); } lo << lt("raw memfunc  std::func") << endBenchmark() << endl;
        }
        {
            FastFunc<void(int&)> t1(&testStr, &TestStruct::rawMemFunc);
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(tempVar); } lo << lt("raw memfunc  don_delegate") << endBenchmark() << endl;
        }
        {
            delegate<void()> t1 = [&]{ testStr.rawMemFunc(tempVar); };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(); } lo << lt("raw memfunc  staticdelegate") << endBenchmark() << endl;
        } lo << endl;

        {
            auto t1 = [](int i){ tempGlobalState = !tempGlobalState; return tempGlobalVar + i + 1; };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(i); } lo << lt("trivial auto") << endBenchmark() << endl;
        }
        {
            function<int(int)> t1 = [](int i){ tempGlobalState = !tempGlobalState; return tempGlobalVar + i + 1; };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(i); } lo << lt("trivial std::func") << endBenchmark() << endl;
        }
        {
            FastFunc<int(int)> t1 = [](int i){ tempGlobalState = !tempGlobalState; return tempGlobalVar + i + 1; };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(i); } lo << lt("trivial don_delegate") << endBenchmark() << endl;
        }
        {
            delegate<int(int)> t1 = [](int i){ tempGlobalState = !tempGlobalState; return tempGlobalVar + i + 1; };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(i); } lo << lt("trivial staticdelegate") << endBenchmark() << endl;
        } lo << endl;

        {
            function<int(int&, int)> t2 = rawFunc;
            function<void(int)> t1 = [&tempVar, &t2](int i){ tempVar = t2(tempVar, i); };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(i); } lo << lt("capture std::func") << endBenchmark() << endl;
        }
        {
            FastFunc<int(int&, int)> t2 = rawFunc;
            FastFunc<void(int)> t1 = [&tempVar, &t2](int i){ tempVar = t2(tempVar, i); };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(i); } lo << lt("capture don_delegate") << endBenchmark() << endl;
        }
        {
            delegate<int(int&, int)> t2 = rawFunc;
            delegate<void(int)> t1 = [&tempVar, &t2](int i){ tempVar = t2(tempVar, i); };
            startBenchmark(); { for(int i = 0; i < repeats; ++i) t1(i); } lo << lt("capture staticdelegate") << endBenchmark() << endl;
        } lo << endl;
    } lo << endl << tempVar << tempGlobalVar;

    return 0;
}

@slyshykO
Copy link

Can you post working test?

@ajneu
Copy link

ajneu commented Nov 4, 2015

Looks interesting.

But note that "test 6" of Clugston's Demo.cpp (reference), produces a different result than ssvu::FastFunc (in Revision 6):

Here's some comparison-test-code (extracted from Clugston's original; with minor changes (const char*)):

#include <stdio.h>
#include <iostream>
#include "FastDelegate.h"
#include "FastFunc.hpp"

class CBaseClass {
protected:
    const char *m_name;
public:
    CBaseClass(const char *name) : m_name(name) {};
    void SimpleMemberFunction(int num, const char *str) {
        printf("In SimpleMemberFunction in %s. Num=%d, str = %s\n", m_name, num, str);  }
    int SimpleMemberFunctionReturnsInt(int num, const char *str) {
        printf("In SimpleMemberFunction in %s. Num=%d, str = %s\n", m_name, num, str); return -1;   }
    void ConstMemberFunction(int num, const char *str) const {
        printf("In ConstMemberFunction in %s. Num=%d, str = %s\n", m_name, num, str);   }
    virtual void SimpleVirtualFunction(int num, const char *str) {
        printf("In SimpleVirtualFunction in %s. Num=%d, str = %s\n", m_name, num, str); }
    static void StaticMemberFunction(int num, const char *str) {
        printf("In StaticMemberFunction. Num=%d, str =%s\n", num, str); }
};

class COtherClass {
    double rubbish; // to ensure this class has non-zero size.
public:
    virtual void UnusedVirtualFunction(void) { }
    virtual void TrickyVirtualFunction(int num, const char *str)=0;
};

class VeryBigClass {
    int letsMakeThingsComplicated[400];
};

// This declaration ensures that we get a convoluted class heirarchy.
class CDerivedClass : public VeryBigClass, virtual public COtherClass, virtual public CBaseClass
{
    double m_somemember[8];
public:
    CDerivedClass() : CBaseClass("Base of Derived") { m_somemember[0]=1.2345; }
    void SimpleDerivedFunction(int num, const char *str) { printf("In SimpleDerived. num=%d\n", num); }
    virtual void AnotherUnusedVirtualFunction(int num, const char *str) {}
    virtual void TrickyVirtualFunction(int num, const char *str) {
        printf("In Derived TrickyMemberFunction. Num=%d, str = %s\n", num, str);
    }
};

using namespace fastdelegate;

int main(void)
{
   using FF = ssvu::FastFunc<void(int, const char *)>;
   using FD = FastDelegate2<int, const char *>;

   FF ssvu_ff;
   FD fastdele_ff;


   CDerivedClass d;


   ssvu_ff = FF(&d, &CBaseClass::SimpleVirtualFunction);
   fastdele_ff = MakeDelegate(&d, &CBaseClass::SimpleVirtualFunction);

   std::cout << "Calling ssvu_ff: >>> ";
   ssvu_ff(6, "asdf");
   std::cout << "<<<" << std::endl;

   std::cout << "Calling fastdele_ff: >>> ";
   fastdele_ff(6, "asdf");
   std::cout << "<<<" << std::endl;
}

@ajneu
Copy link

ajneu commented Nov 4, 2015

Which platforms are you running? Do you have the same (differing) behaviour?

Since the code uses some non-standard hacks... I should mention my platform

c++ -v       ## g++ -v

shows

Using built-in specs.
COLLECT_GCC=c++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 5.2.1-23' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --with-arch-32=i586 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.2.1 20151028 (Debian 5.2.1-23)

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