New in 4.26! TDelegate and TMulticastDelegate syntax

Unreal Engine 4.26 Release Notes: New: Added TDelegate and TMulticastDelegate syntax, similar to TFunction, to be used instead of the DECLARE_DELEGATE and DECLARE_MULTICAST_DELEGATE macros.
From Unreal Engine 4.26 Release Notes

In a nutshell, in 4.26, you can now use TDelegate instead of

  • DECLARE_DELEGATE
  • DECLARE_DELEGATE_OneParam
  • DECLARE_DELEGATE_RetVal
  • DECLARE_DELEGATE_RetVal_OneParams
  • DECLARE_DELEGATE_TwoParams
  • DECLARE_DELEGATE_RetVal_TwoParams
  • ETC.

TMulticastDelegate uses the same template syntax as TDelegate, and also supersedes the following macros:

  • DECLARE_MULTICAST_DELEGATE
  • DECLARE_MULTICAST_DELEGATE_OneParam
  • DECLARE_MULTICAST_DELEGATE_RetVal
  • DECLARE_MULTICAST_DELEGATE_RetVal_OneParam
  • DECLARE_MULTICAST_DELEGATE_TwoParams
  • DECLARE_MULTICAST_DELEGATE_RetVal_TwoParams
  • ETC.

Quick usage example

Instead of writing:

DECLARE_DELEGATE_TwoParams(FTest, UObject* Target, FVector Location)

You can now do this, which is exactly the same:

using FTest = TDelegate<void(UObject* Target, FVector Location)>;

// or

typedef TDelegate<void(UObject* Target, FVector Location)> FTest;

How it works - The template syntax

TDelegate and TMulticastDelegate follow the same template syntax as TFunction.
If you used TFunction before, there is no need to read any further.

Here is a simplified overview of the TDelegate and TMulticastDelegate specialization we're using:

// TDelegate, simplified overview
template <typename InRetValType, typename... ParamTypes, typename UserPolicy> class TDelegate<InRetValType(ParamTypes...), UserPolicy>

// TMulticastDelegate, simplified overview - Must return void
template <typename RetValType, typename... ParamTypes, typename UserPolicy>
class TMulticastDelegate<RetValType(ParamTypes...), UserPolicy>

UserPolicy = FDefaultDelegateUserPolicy. It's is an optional policy that gives users room for any custom extensions. More documentation about it can be read inside FDefaultDelegateUserPolicy (Core module Delegates/DelegateBase.h).

The way it works is very straight to the point. The template instantiation basically defines the delegate's function signature.

After you specify the return value type, you can add arguments by supplying them in parenthesis:

TDelegate<ReturnValueType(ArgType, ArgType, Etc)>
TMulticastDelegate<void(ArgType, ArgType, Etc)>

TMulticastDelegate must return void

Here's a table to quickly grasp the usage pattern:

Return void TDelegate<void>;
Return void, one argument TDelegate<void(bool Arg)>;
Return void, two arguments TDelegate<void(bool Arg1, float Arg2)>;
Return UObject* TDelegate<UObject*>;
Return UObject*, one argument TDelegate<UObject*(FVector Arg)>;
Return UObject*, two arguments TDelegate<UObject*(FVector Arg1, float Arg2)>;

Why I highly prefer this over using macros

Seeing the native-only usage, there is simply no practical reason anymore to have a variety of macros wrap each possibility up (other than sticking to legacy preference). It is clear that using TFunction's nifty syntax will create more consistency among everything that implies statically defining a function signature.

On the other hand, because DECLARE_DYNAMIC_ delegate variants require reflection code to be generated (such as hidden structs, housing all the delegate's arguments) for blueprint VM execution, and an on-the-spot class definition with inlined, non-virtual execution methods, using a macro is extremely neat.