Skip to content

Instantly share code, notes, and snippets.

@romualdo97
Last active October 28, 2022 19:41
Show Gist options
  • Save romualdo97/9482b924bab9644cbe914f92b607e670 to your computer and use it in GitHub Desktop.
Save romualdo97/9482b924bab9644cbe914f92b607e670 to your computer and use it in GitHub Desktop.
Simplified and cloned Unreal TDelegate class for study
#pragma once
#include <memory>
/* ======================================================================= */
/* Base delegateInstance interface */
template <typename FuncType>
struct IBaseDelegateInstance;
template <typename RetType, typename... ArgTypes>
struct IBaseDelegateInstance<RetType(ArgTypes...)>
{
/**
* Execute the delegate. If the function pointer is not valid, an error will occur.
*/
virtual RetType Execute(ArgTypes...) const = 0;
};
/* ======================================================================= */
/* Generate the Method ptr type */
template <bool Const, typename Class, typename FuncType>
struct TMemFunPtrType;
template <typename Class, typename RetType, typename... ArgTypes>
struct TMemFunPtrType<false, Class, RetType(ArgTypes...)>
{
typedef RetType(Class::* Type)(ArgTypes...);
};
template <typename Class, typename RetType, typename... ArgTypes>
struct TMemFunPtrType<true, Class, RetType(ArgTypes...)>
{
typedef RetType(Class::* Type)(ArgTypes...) const;
};
/**
* Returns the same type passed to it. This is useful in a few cases, but mainly for inhibiting template argument deduction in function arguments, e.g.:
*
* template <typename T>
* void Func1(T Val); // Can be called like Func(123) or Func<int>(123);
*
* template <typename T>
* void Func2(typename TIdentity<T>::Type Val); // Must be called like Func<int>(123)
*/
template <typename T>
struct TIdentity
{
typedef T Type;
};
/* ======================================================================= */
/**
* Implements a delegate binding for C++ member functions.
*/
template <bool bConst, class UserClass, typename FuncType>
class TBaseRawMethodDelegateInstance;
template <bool bConst, class UserClass, typename WrappedRetValType, typename... ParamTypes>
class TBaseRawMethodDelegateInstance<bConst, UserClass, WrappedRetValType(ParamTypes...)>
: public IBaseDelegateInstance<WrappedRetValType(ParamTypes...)>
{
public:
using FMethodPtr = typename TMemFunPtrType<bConst, UserClass, WrappedRetValType(ParamTypes...)>::Type;
TBaseRawMethodDelegateInstance(UserClass* InUserObject, FMethodPtr InMethodPtr)
: UserObject(InUserObject), MethodPtr(InMethodPtr)
{
// Non-expirable delegates must always have a non-null object pointer on creation (otherwise they could never execute.)
// check(InUserObject != nullptr && MethodPtr != nullptr);
}
WrappedRetValType Execute(ParamTypes... Params) const override
{
return (UserObject->*MethodPtr)(Params...);
//return std::invoke(MethodPtr, ParamTypes...);
}
private:
UserClass* UserObject;
FMethodPtr MethodPtr;
};
/* ======================================================================= */
/**
* Implements a delegate binding for C++ free functions.
*/
template <typename FuncType>
class TBaseFreeFunctionDelegateInstance;
template <typename WrappedRetValType, typename... ParamTypes>
class TBaseFreeFunctionDelegateInstance<WrappedRetValType(ParamTypes...)>
: public IBaseDelegateInstance<WrappedRetValType(ParamTypes...)>
{
public:
using FFreeFunctionPtr = WrappedRetValType(*)(ParamTypes...);
TBaseFreeFunctionDelegateInstance(FFreeFunctionPtr InFuncPtr)
: FuncPtr(InFuncPtr)
{
}
WrappedRetValType Execute(ParamTypes... Params) const override
{
return (*FuncPtr)(Params...);
}
private:
FFreeFunctionPtr FuncPtr;
};
/* ======================================================================= */
/**
* Unicast delegate template class.
*
* Use the various DECLARE_DELEGATE macros to create the actual delegate type, templated to
* the function signature the delegate is compatible with. Then, you can create an instance
* of that class when you want to bind a function to the delegate.
*/
template<typename DelegateSignature>
class TMyDelegate
{
static_assert(sizeof(DelegateSignature) == 0, "Expected a function signature for the delegate template parameter");
};
template<typename InRetValType, typename... ParamTypes>
class TMyDelegate<InRetValType(ParamTypes...)>
{
public:
using FuncType = InRetValType(ParamTypes...);
using IBaseDelegateInstanceType = IBaseDelegateInstance<FuncType>;
typedef InRetValType RetValType;
/** Declare the user's C++ pointer-based delegate instance types. */
template <typename UserClass>
struct TRawMethodDelegate : TBaseRawMethodDelegateInstance<false, UserClass, FuncType>
{
typedef TBaseRawMethodDelegateInstance<false, UserClass, FuncType> Super;
TRawMethodDelegate(UserClass* InUserObject, typename Super::FMethodPtr InMethodPtr)
: Super(InUserObject, InMethodPtr)
{}
};
struct TFreeFunctionDelegate : TBaseFreeFunctionDelegateInstance<FuncType>
{
typedef TBaseFreeFunctionDelegateInstance<FuncType> Super;
TFreeFunctionDelegate(typename Super::FFreeFunctionPtr InMethodPtr)
: Super(InMethodPtr)
{}
};
/**
* Static: Creates a raw C++ pointer member function delegate.
*
* Raw pointer doesn't use any sort of reference, so may be unsafe to call if the object was
* deleted out from underneath your delegate. Be careful when calling Execute()!
*/
template <typename UserClass>
inline static TMyDelegate<RetValType(ParamTypes...)> CreateRaw(UserClass* InUserObject, typename TMemFunPtrType<false, UserClass, RetValType(ParamTypes...)>::Type InFunc)
{
TMyDelegate<RetValType(ParamTypes...)> Result;
// Instance the Delegate instance
Result.LocalDelegateInstance.reset(new TBaseRawMethodDelegateInstance<false, UserClass, FuncType>(InUserObject, InFunc));
return Result;
}
inline static TMyDelegate<RetValType(ParamTypes...)> CreateFreeFunction(typename TIdentity<RetValType(*)(ParamTypes...)>::Type InFunc)
{
TMyDelegate<RetValType(ParamTypes...)> Result;
// Instance the Delegate instance
Result.LocalDelegateInstance.reset(new TBaseFreeFunctionDelegateInstance<FuncType>(InFunc));
return Result;
}
inline void Execute(ParamTypes... Params)
{
return LocalDelegateInstance->Execute(Params...);
}
private:
std::shared_ptr<IBaseDelegateInstanceType> LocalDelegateInstance;
};
// MyOwnDelegates.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include <iostream>
#include "DelegateSignatureImpl.h"
/* Dummy class with a method that will be bound to a delegate*/
class MyClass
{
public:
void HandleSomething(int Num, float Num2, const std::string& Name)
{
std::cout << "Handling something on member function listener with " << Num << " and " << Num2 << " by " << Name << std::endl;
}
};
/* Dummy free function that will be bound to a delegate*/
void HandleSomethingFromFreeFunction(int Num, float Num2, const std::string& Name)
{
std::cout << "Handling something on free function listener with " << Num << " and " << Num2 << " by " << Name << std::endl;
}
int main()
{
MyClass obj;
using MyDelegateSignature = TMyDelegate<void(int, float, const std::string&)>;
// An invocation list whose target are member function and free functions
MyDelegateSignature MultiCastInvocationList[2]{
MyDelegateSignature::CreateRaw(&obj, &MyClass::HandleSomething),
MyDelegateSignature::CreateFreeFunction(&HandleSomethingFromFreeFunction)
};
// Invoke all the delegates (somehow similar to unreal multi-cast delegates)
for (MyDelegateSignature& Delegate : MultiCastInvocationList)
{
Delegate.Execute(5, 10.0f, "Romualdo Villalobos");
}
}
@romualdo97
Copy link
Author

romualdo97 commented Oct 27, 2022

This is a simple TDelegate class clone extracted manually from the Unreal source code to understand/study how does unreal implements its delegates system.

Here I only extracted the minimum code to allow delegate to Member functions on raw C++ objects and delegate to global free function, and ignored Unreal delegate payload system and sophisticated memory handling to focus only on the basic class hierarchy and how distinct template specialization works together.

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