[Request] declaring templated virtual Broadcast function in FMulticastDelegateBase

Hey Epic guys,

I have a the following request:

Would it be possible to add a Broadcast Function to FMulticastDelegateBase?
I’m not really good in templates, but wouldn’t it be enough to just add:

template<typename ...ParamTypes>
virtual void Broadcast(ParamTypes... Params) const
{
	//...
}

I need this, because I want to make my code more generic.

I have these Delegate

DECLARE_MULTICAST_DELEGATE				(FUIOnPressed);
DECLARE_MULTICAST_DELEGATE				(FUIOnReleased);
DECLARE_MULTICAST_DELEGATE				(FUIOnBeginHover);
DECLARE_MULTICAST_DELEGATE				(FUIOnHover);
DECLARE_MULTICAST_DELEGATE				(FUIOnEndHover);
DECLARE_MULTICAST_DELEGATE_OneParam		(FUIOnValueChanged, float);

and this struct:

USTRUCT(BlueprintType)
struct FActionDelegates
{
	GENERATED_BODY()
		//Please keep right Order
	FUIOnPressed	  OnPressed;
	FUIOnReleased	  OnReleased;
	FUIOnBeginHover	  OnBeginHover;
	FUIOnHover		  OnHover;
	FUIOnEndHover	  OnEndHover;
	FUIOnValueChanged OnValueChanged;

    //Broadcast 1
	void Broadcast(EUIInputEvent InputEvent)
	{
		switch (InputEvent)
		{
		case EUIInputEvent::UI_Pressed:
			OnPressed.Broadcast();
			break;
		case EUIInputEvent::UI_BeginHover:
			OnBeginHover.Broadcast();
			break;
		case EUIInputEvent::UI_Hover:
			OnHover.Broadcast();
			break;
		case EUIInputEvent::UI_EndHover:
			OnEndHover.Broadcast();
			break;
		case EUIInputEvent::UI_Released:
			OnReleased.Broadcast();
			break;
		default:
			break;
		}
	}
    //Broadcast 2
	void Broadcast(EUIInputEvent InputEvent, float Value)
	{
		switch (InputEvent)
		{
		case EUIInputEvent::UI_ValueChanged:
			OnValueChanged.Broadcast(Value);
		}
	}

	void AddFunction(EUIInputEvent InputEvent, UObject* Object, FName InFunctionName)
	{
		switch (InputEvent)
		{
		case EUIInputEvent::UI_Pressed:
			OnPressed.AddUFunction(Object, InFunctionName);
			break;
		case EUIInputEvent::UI_BeginHover:
			OnBeginHover.AddUFunction(Object, InFunctionName);
			break;
		case EUIInputEvent::UI_Hover:
			OnHover.AddUFunction(Object, InFunctionName);
			break;
		case EUIInputEvent::UI_EndHover:
			OnEndHover.AddUFunction(Object, InFunctionName);
			break;
		case EUIInputEvent::UI_Released:
			OnReleased.AddUFunction(Object, InFunctionName);
			break;
		case EUIInputEvent::UI_ValueChanged:
			OnValueChanged.AddUFunction(Object, InFunctionName);
		default:
			break;
		}
	}
};

For every type of Delegate I need a new Broadcast Function which helps me Broadcasting this Delegate.
Fortunately most of my Delegates have the same Signature (No Params) but if I want to add for example a Delegate which has a String as a Parameter I have to add another Overloaded Broadcast Function.

I made it like this, because my first attempt didn’t work, because FMulticastDelegateBase doesn’t have a Broadcast Function.

I wanted to do it like this:
//In FActionDelegates
FMethodDelegateBase* GetDelegate(EUIInputEvent Event)
{
switch(Event):
return &OnPressed;
//…and so on
}

//Called like this
template<typename... ParamTypes>
void NotifyEvent(FName ElementName, EUIInputEvent InputEvent, ParamTypes... Params)
{
    //ActionDelegates are stored in:
    //TMap<FName, FActionDelegates>  Delegates; 
	if (auto ElementDelegates = Delegates.Find(ElementName))
	{
            //ElementDelegates is of struct type FActionDelegates
	    ElementDelegates->GetDelegate(InputEvent)->Broadcast(Params...);
	}
}

Sry for the wall of code :stuck_out_tongue:

If it’s not possible to do so, it would be nice to get a good exaplanation to get more into templates :slight_smile:

Thank you

Greetings

Hi,

Virtual functions cannot be templates.

Steve

Seems legit…
Thanks :slight_smile:

But do you maybe see a way to make my attempt more generic??

Greetings

The problem here is that the code doing the broadcast and the code doing the binding are entirely separate, so there is no way to match up the arguments, except perhaps by doing some kind of implicit runtime conversion, like a scripting language might do.

For example, what would you expect to happen here?

NotifyEvent(Element, TEXT("OnReleased"), FString("Hello")); // string passed, but FUIOnReleased takes no parameters

NotifyEvent(Element, TEXT("OnValueChanged")); // nothing passed, but FUIOnValueChanged expects a float

NotifyEvent(Element, TEXT("OnValueChanged"), TEXT("3.14")); // a string passed, but FUIOnValueChanged expects a float

NotifyEvent(Element, TEXT("OnValueChanged"), ActorPtr); // UActor* passed, but FUIOnValueChanged expects a float

In the first case, you might expect the argument to be ignored. That’s not too bad.

In the second case, you might expect the parameter to get a default-constructed object. This might work, except not all types are default constructible.

In the third case, you might expect the string to be converted to a float. This comes at considerable runtime cost, and not every string will be parsable as a float, so you’d need to be able to optionally report an error.

In the last case, you’d probably expect a type conversion exception or some other form of failure report. Silent failure would be bad, as would exceptions in a codebase where exceptions are disabled and the code is not written with them in mind.

Simply, this is not the kind of thing C++ is good at. To do this stuff, we’d have to do reimplement the C++ type system at runtime.

Steve

Rather than identifying delegates by name, perhaps you’d be better off passing member function pointers:

template <typename DelegateType, typename... ArgTypes)
void NotifyEvent(FName ElementName, DelegateType DelegateMemFn, ArgTypes&&... Args)
{
    if (auto ElementDelegates = Delegates.Find(ElementName))
    {
        (ElementDelegates->*DelegateMemFn)(Forward<ArgTypes>(Args)...);
    }
}

NotifyEvent(Element, &FActionDelegates::FUIOnHover);
NotifyEvent(Element, &FActionDelegates::FUIOnValueChanged, 3.14f);

This would work as both the delegate type and the arguments you are passing to it are known to the compiler at the call point, and so the type system would work for you. Conversions would happen automatically and the wrong number of arguments or incompatible types would cause a compile error.

This is less flexible than a fully runtime type system, but may get you far enough. You can achieve simple polymorphism of the delegate type as long as you restrict yourself to delegates with the same signature, e.g. FUIOnPressed, FUIOnReleased etc. above, but not FUIOnValueChanged.

Such an approach would not be usable from blueprints, however, as member function pointers are not a supported reflection type.

Hope this helps,

Steve

Wow thank you very much.
There is so much I have to learn about templates

Actually I tried passing Function Pointer but I had to give it up, because I didn’t really know how to do it.

I’ll try your example, and look into each step to actually understand what happens.

Thank you
Greetings