x

Search in
Sort by:

Question Status:

Search help

  • Simple searches use one or more words. Separate the words with spaces (cat dog) to search cat,dog or both. Separate the words with plus signs (cat +dog) to search for items that may contain cat but must contain dog.
  • You can further refine your search on the search results page, where you can search by keywords, author, topic. These can be combined with each other. Examples
    • cat dog --matches anything with cat,dog or both
    • cat +dog --searches for cat +dog where dog is a mandatory term
    • cat -dog -- searches for cat excluding any result containing dog
    • [cats] —will restrict your search to results with topic named "cats"
    • [cats] [dogs] —will restrict your search to results with both topics, "cats", and "dogs"

[C++] Pass a class member function to another function?

I have a class set up with a delegate that other classes can bind to like this.

 Publisher->MyDelegate.BindUObject(this, &ASubscriber::MyDelegateFunction);

This all works fine without a problem. However, what I would like to do is pass the delegate function directly to the publisher class and let it decide how to bind and do any other operations it wants to do. Something like this for example.

 Publisher->PerformBind(this, &ASubscriber::MyDelegateFunction);

Then internally, the publisher could do the binding like this.

 MyDelegate.BindUObject(PassedInClass, PassedInFunction);

So I'm just wondering how I go about passing the ASubscriber class instance and its delegate function to my publisher class.

Product Version: UE 4.15
Tags:
more ▼

asked Mar 19 '17 at 03:09 AM in C++ Programming

avatar image

wilberolive
318 30 45 48

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

1 answer: sort voted first

You can do the following:

Header:

 DECLARE_DELEGATE_RetVal_OneParam(float, FMyDelegate, int);
 
 UCLASS()
 class USubscriber : public UObject
 {
     GENERATED_BODY()
 
 public:
     USubscriber();
 
     virtual float MyDelegateFunction(int Time);
     virtual float MyOtherDelegateFunction(int Time);
 
     float TimeMultiplier;
 };
 
 UCLASS()
 class USlowSubscriber : public USubscriber
 {
     GENERATED_BODY()
 
 public:
     float MyDelegateFunction(int Time) override;
 };
 
 UCLASS()
 class UPublisher : public UObject
 {
     GENERATED_BODY()
 
 public:
     FMyDelegate MyDelegate;
     
     void BindUObject(USubscriber* Subscriber, float (USubscriber::* GetTime)(int Time));
     void BindUClass(TSubclassOf<USubscriber> Class, float (USubscriber::* GetTime)(int Time));
 };
 

Implementation:

 USubscriber::USubscriber()
     : TimeMultiplier{ 10.f }
 {
 }
 
 float USubscriber::MyDelegateFunction(int Time)
 {
     return TimeMultiplier * Time;
 }
 
 float USubscriber::MyOtherDelegateFunction(int Time)
 {
     return Time / TimeMultiplier;
 }
 
 float USlowSubscriber::MyDelegateFunction(int Time)
 {
     UE_LOG(LogTemp, Warning, TEXT("I'm really slow..."));
     return TimeMultiplier * 100.f * Time;
 }
 
 void UPublisher::BindUObject(USubscriber* Subscriber, float (USubscriber::* GetTime)(int Time))
 {
     if (Subscriber)
     {
         MyDelegate.BindUObject(Subscriber, GetTime);
     }
     if (MyDelegate.IsBound())
     {
         UE_LOG(LogTemp, Warning, TEXT("The value of 'MyDelegate.Execute(2)' is: %f"), MyDelegate.Execute(2));
     }
 
 }
 
 void UPublisher::BindUClass(TSubclassOf<USubscriber> Class, float (USubscriber::* GetTime)(int Time))
 {
     USubscriber* Subscriber{ NewObject<USubscriber>(this, Class) };
     if (ensure(Subscriber))
     {
         MyDelegate.BindUObject(Subscriber, GetTime);
     }
     if (ensure(MyDelegate.IsBound()))
     {
         UE_LOG(LogTemp, Warning, TEXT("The value of 'MyDelegate.Execute(2)' is: %f"), MyDelegate.Execute(2));
     }
 }

And then...

 UPublisher* Publisher{ NewObject<UPublisher>(this) };

 USubscriber* Subscriber{ NewObject<USubscriber>(this) };
 Subscriber->TimeMultiplier = 10.f;

 USubscriber* AnotherSubscriber{ NewObject<USubscriber>(this) };
 AnotherSubscriber->TimeMultiplier = 2.f;

 Publisher->BindUObject(Subscriber, &USubscriber::MyDelegateFunction);
 Publisher->BindUObject(Subscriber, &USubscriber::MyOtherDelegateFunction);
 Publisher->BindUObject(AnotherSubscriber, &USubscriber::MyDelegateFunction);
 Publisher->BindUObject(AnotherSubscriber, &USubscriber::MyOtherDelegateFunction);

 Publisher->BindUClass(USubscriber::StaticClass(), &USubscriber::MyDelegateFunction);
 Publisher->BindUClass(USlowSubscriber::StaticClass(), &USubscriber::MyDelegateFunction);

will output

 LogTemp:Warning: The value of 'MyDelegate.Execute(2)' is: 20.000000
 LogTemp:Warning: The value of 'MyDelegate.Execute(2)' is: 0.200000
 LogTemp:Warning: The value of 'MyDelegate.Execute(2)' is: 4.000000
 LogTemp:Warning: The value of 'MyDelegate.Execute(2)' is: 1.000000
 LogTemp:Warning: The value of 'MyDelegate.Execute(2)' is: 20.000000
 LogTemp:Warning: I'm really slow...
 LogTemp:Warning: The value of 'MyDelegate.Execute(2)' is: 2000.000000






more ▼

answered Mar 20 '17 at 03:37 PM

avatar image

Matthias Hölzl
416 13 6 19

avatar image wilberolive Mar 21 '17 at 06:17 AM

But that means the publisher has to know about the subscriber right as you have USubscriber in your UObjectBind function declaration. I'm trying to find a solution that avoids that. The publisher is actually a UActorComponent, so it can be attached to any AActor in the scene, so it has no idea what the actor's class type will be.

In BeginPlay, an actor can attach a publisher component to itself and bind to the publisher's delegate. The problem is that this puts the responsibility on to the actor to know how to bind itself to the publisher. I'm hoping to set up a static function on the publisher that the actor can call, like this for example.

 static UPublisherComponent* Create(UObject* Owner, void (UObject::* DelegateFunction)(int32& ID));

The above code doesn't actually work, but its what I'm trying to achieve. The actor could then make a call like this in BeginPlay.

 MyPublisher = UPublisherComponent::Create(this, &AMyActor::MyDelegateFunction);

The actor would then have a function like this.

 void AMyActor::MyDelegateFunction(int32& ID)
 {
 }

When I do try to use the above code I get the following error. UPublisherComponent UPublisherComponent::Create(UObject ,void (__cdecl UObject:: )(int32 &))': cannot convert argument 2 from 'void (__cdecl AMyActor:: )(int32 &)' to 'void (__cdecl UObject::* )(int32 &)'

avatar image Matthias Hölzl Mar 21 '17 at 09:09 AM

Well, it would be pretty simple to get rid of the dependency on USubscriber in the publisher: just turn BindUObject and BindUClass into templates:

 UCLASS()
 class UPublisher : public UObject
 {
     GENERATED_BODY()
 
 public:
     FMyDelegate MyDelegate;
     
     template<typename Subscriber>
     void BindUObject(Subscriber* Subscriber, float (Subscriber::* GetTime)(int Time));
 
     template<typename Subscriber>
     void BindUClass(UClass* Class, float (Subscriber::* GetTime)(int Time));
 };
 
 
 template<typename Subscriber>
 void UPublisher::BindUObject(Subscriber* InSubscriber, float (Subscriber::* GetTime)(int Time))
 {
     if (InSubscriber)
     {
         MyDelegate.BindUObject(InSubscriber, GetTime);
     }
     if (MyDelegate.IsBound())
     {
         UE_LOG(LogTemp, Warning, TEXT("The value of 'MyDelegate.Execute(2)' is: %f"), MyDelegate.Execute(2));
     }
 
 }
 
 template<typename Subscriber>
 void UPublisher::BindUClass(UClass* Class, float (Subscriber::* GetTime)(int Time))
 {
     Subscriber* MySubscriber{ NewObject<Subscriber>(this, Class) };
     if (ensure(MySubscriber))
     {
         MyDelegate.BindUObject(MySubscriber, GetTime);
     }
     if (ensure(MyDelegate.IsBound()))
     {
         UE_LOG(LogTemp, Warning, TEXT("The value of 'MyDelegate.Execute(2)' is: %f"), MyDelegate.Execute(2));
     }
 }

Then you can use BindUClass and BindUObject for any kind of object/class and corresponding member function. E.g., the following works for a class UFooBar that is unrelated to the original USubscriber:

     Publisher->BindUClass(USubscriber::StaticClass(), &USubscriber::MyDelegateFunction);
     Publisher->BindUClass(USlowSubscriber::StaticClass(), &USubscriber::MyDelegateFunction);
     Publisher->BindUClass(UFooBar::StaticClass(), &UFooBar::SomethingDifferent);

If I understand correctly that should be sufficient for what you want to achieve.

avatar image wilberolive Mar 22 '17 at 03:06 AM

Ah, templates, yes that seems to solve half the problem. Unfortunately the BindUObject on the delegate complains.

Here what my test function looks like.

 template<typename ActorType>
 static UPublisherComponent* Create(UObject* Owner, void (ActorType::* DelegateFunction)(int32& ID))
 {
     UPublisherComponent* Component;
     // Bunch of other code here for setting up the component...
     Component->DelegateFunction.BindUObject(Owner, DelegateFunction);
     return Component;
 }

When I call this function, I get the following error on the BindUObject line. Any other ideas up your sleeve?

 void TBaseDelegate<TTypeWrapper<void>,int32 &>::BindUObject<UObject,>(UserClass *,void (__cdecl UObject::* )(int32 &) const)': cannot convert argument 2 from 'void (__cdecl AMyActor::* )(int32 &)' to 'void (__cdecl UObject::* )(int32 &)
avatar image Matthias Hölzl Mar 22 '17 at 03:58 AM

Hmm, do you really need the type of Owner to be different from ActorType*? In that case I'm afraid you're out of luck in C++. Even if you could somehow trick the compiler into accepting your code (by casting in the body of the function or something equally nasty) I'm pretty certain that converting member pointers to anything else (or applying them to anything that is not an instance of their defining class) leads to undefined behavior.

OK, there is one case in which passing in an UObject* Owner and then binding the delegate to that object should be fairly easy: If you want to have the UObject* type in the interface of your function to make it easier for callers to do something like BindUObject(GetSomeUObjectThatServesAsOwner(), ...) without having to explicitly cast the first argument. If the owner they pass in is always of the correct type for the delegate you can add a cast in the body of your function, and since the Unreal reflection system provides a dynamically type checked cast we don't lose a lot:

 Component->DelegateFunction.BindUObject(Cast<ActorType>(Owner), DelegateFunction);

I think defining the function with ActorType* Owner and having users explicitly cast to the correct type is the better solution in this case, since it makes it clearer from the interface of the function that you cannot pass in a first argument that is not compatible with the delegate.

But if you really need that kind of "duck typing" that allows you to invoke a member function of AActor on an UObject that might not be an actor (or similar constellations), you're probably much better off with linking Unreal.js or UnrealEnginePython into your code and writing your components in a dynamic language. Especially since I'm guessing that if you need this kind of flexibility to initialize your actors, you will need it even more to implement their functionality.

avatar image wilberolive Mar 22 '17 at 08:02 AM

Ah ha! You are right. No, I don't need that to be UObject, I just simply forgot to change it to ActorType. It now works. Thanks so much for your help buddy.

avatar image Matthias Hölzl Mar 22 '17 at 12:33 PM

Glad to hear it worked. Good luck with your project!

(comments are locked)
10|2000 characters needed characters left
Viewable by all users
Your answer
toggle preview:

Up to 5 attachments (including images) can be used with a maximum of 5.2 MB each and 5.2 MB total.

Follow this question

Once you sign in you will be able to subscribe for any updates here

Answers to this question