How does FString work as a param for Delegate?

I was messing around with Dynamic Multicast delegates and I noticed something that seems quite strange to me:
This works:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FStringDelegate, FString, String);

But this don’t:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FStringDelegate, UTestyData, Data);
UHT will raise an error: Found 'end of type' when expecting '*' [UnrealHeaderTool Error]
The error will disapear if I use UTestyData* instead, which lead to the question: “Why doesn’t UHT raise the same error on FString?”

And after checking the source code for FString, the first thing that makes no sense to me is that FString is not even a UClass! I was alway under the belive that you have to use a uclass/uenum type for all the params in a delegate.
Creating a new FTestyData that is not makred as UClass and try using it in the delegate will cause UHT to raise the error Unable to find 'class', 'delegate', 'enum', or 'struct' with name 'FTestyData' [UnrealHeaderTool Error]
So why can we use FString in a delegate??

Here’s the code for UTestyData just in case:

UCLASS(Blueprintable)
class UTestyData : public UObject
{
	GENERATED_BODY()

public:
	UTestyData () = default;
	UTestyData (const UTestyData &) = default;
};

I am really confused about this. Can someone help explain this? Am I mis-understanding about some important about delegate?

If you try to compile your UObject derived code

	UTestyData (const UTestyData &) = default;

will cause a compile error.
Remove it and it should compile fine.
The delegate also compiles fine once you remove that part of the code.

And FStringDelegate has nothing to do with FString, it’s just a name that you assign the delegate. It’s just a handle you can use to bind / unbind.

Nope, removing that still causees The same UHT error:


So the question still remains: why FString can be used as a param in delegate? And why doesn’t FString needs a pointer?

You are clearly declaring the delegate inside of your class (noticed the public: above it).
You need to put it at the top of the file above the UClass declaration

Sorry, but moving it out still does nothing:


PS:
You are using FString, and I know FString works.
I am wondering: Why FString works, but my custom class doesn’t

Ok sorry I was misled by the fsting delegate first. It’s expecting a * so a pointer

You can only pass in a direct object if it was a Struct not a uobject derived.

Yes, I know adding a pointer works…
But WHY?
Why my custom type need to use pointer while FString don’t??
And Why FString works in the first place when it’s not even a UCLASS???

I’m really confused…

FString as the prefix suggests is a struct. All structs in unreal are preceded with the letter F. It’s an unreal struct wrapper around c++ string. That is why it can be passed directly. There is no instance (no “new” object) if it. So no pointer.

Its a matter of stack vs heap. You do not access structs through addressing their memory location via pointers (*)

  1. FString is defined as a class in UnrealString.h:
    image

  2. A custom made normal struct still raises the error:

  3. A custom made USTRUCT works:

Which leads to an extra question:
Why USTRUCT works with no pointer but UCLASS have to use a pointer?

  1. Did a search in UnrealString.h, there’s no mention of “UStruct”:
    image
    So the question remains: WHY does FString work??

You are getting errors because you are deriving from UOBJECT.

FString is a inside of the core unreal properties.

Nowhere inside of Unrealstring.h can you see any type of UOBJECT or anything derived from it. The naming convention implies that it follows the struct build.

And all of Fstring internals are based on struct operations.

edit: Either have your data be a struct or don’t derive from UObject (or if you must have UObject then just use a pointer). It’s as simple as that.

The naming convention implies that it follows the struct build

The fact that my custom normal class/struct that follows the same naming convention still get reject by UHT means that there must be some code that does something to FString, or DECLARE_XXXX have some special checks on FString. Right?

FString is a inside of the core unreal properties.

So can I assume that there’re code somewhere that makes FString acceptable by the delegate macros?

If so, where are these codes? Can I implement something in my own class so it can be acceptable too?

Either have your data be a struct or don’t derive from UObject

So can I assume that, in the DECLARE_XXX macros, there are code that checks if the param I used is a struct or a class, and will raise an error if it’s a class with no pointer?

It probably just does an IsA test somewhere or cast.

Pulled the UE5.2 source code from github, trying to find the code that raises the pointer error. No result… Weird, where the F are the checks done then…

This is due to the fact that UObject(and all inherited classes) is non-copyable.
You’ll get the same effect if you try to pass as the delegate’s argument simple structure like this:

struct FErrorRaiser
{
  FErrorRaiser() = default;
  // non-copyable
    FErrorRaiser(FErrorRaiser const& Other) = delete;
    FErrorRaiser& operator=(FErrorRaiser const& Other) = delete;
};

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FErrorDelegate, FErrorRaiser, CompilationFailed);

Oh, that makes a lot of sense! Thanks!
But why do UE makes all UObject non copyable? Is it because UObject requires an outer?
Also, what about FString? What does UE did to make FString usable?

UObjects creation path is strictly defined, their lifetime is managed by GC,
Additionally copying is always problematic with inheritance (for example:object slicing).

I don’t quite understand what you’re asking

Thanks a lot on the UObjedt~!

I mean, why can FString, a normal class(not UCLASS), be used as a param for delegate while mine can’t? What is the core part that make FString usable? Can I do it to my own custom classes?

I suppose there is a special path in UHT or something for FString as it’s a fundamental UE type.
I didn’t check, never thought about it :slight_smile:

Yes there is special engine setup that adds a bunch of engine types like FString, FVector, TArray, TMap, etc to the reflection information without using the normal USTRUCT or UCLASS markup.

Maybe, but it’s not clear how to do it. Plenty of us would love to be able to add more templated types but no one outside of Epic has done it (as far as I am aware).
It’s way easier to just use USTRUCT and UCLASS markup instead.

Or use a non-dynamic delegate. Those are only limited by the compiler and not by reflection. The downside is that you can only use them in native.

1 Like