Why do classes exist that cannot call functions from C++ cleanly?

I watched the course here and learned how to convert from Blueprint to C++.

I have attempted to port a simple use case for Quartz to C++.
This is what happens when you port Blueprint’s ResumeClock to C++.

ClockHandle->ResumeClock(this, ClockHandle);

It makes no sense to go to the trouble of passing “this” or to have ClockHandle appear twice in one line.
However, if it is said that it is an interface for Blueprint, it is understandable.

if you look inside this ResumeClock, you will see that it uses a private member called RawHandle, so this C++ code is now complete.

Why do classes exist that cannot call functions from C++ cleanly?
Am I missing something?

the word “cleanly” gets subjective quickly, and for the most part comes down to “readable”, a person that has never seen the versatility of a given language might look at something like:

FRotator DRotation = UKismetMathLibrary::FindLookAtRotation(FollowCamera->GetComponentLocation(), GetActorLocation());	FollowCamera->SetWorldRotation(FMath::RInterpConstantTo(FollowCamera->GetComponentRotation(), DRotation, DeltaTime, CamRotationSpeed));

and be completely baffled, or something involving a Lambda function (where a function is conjured into existence as an argument to another function being called instead of sending an existing function to be called, or just a pointer to an object that a specific function would be called on)

while a highly experienced programmer in a language might still be completely lost by something they themselves wrote the week before because they forgot the tiny intricacies of each member and function being called.

the “Clean Code” Paradigm is about making functions about MyDesciptiveObject.DoThing1(OnlyArg).DoThing2(); which is often abstraction on abstraction where those 2 function calls could be doing 500 magic steps, and we just trust the outcome. When it comes to refactoring, or optimizing these abstractions can create so many knock on effects or edge cases, you either end up rewriting the function, or the function and all the places it is called from. Though clean code often necessitates a tight weaving between the definitions and the implementation, and as systems become more generic we need more information/arguments to actually do the task. “Clean Code” also is very spoken/written language dependent, and potentially dependent on the writer MyObject->GetX() is Clean code, but without a meaningful name on the Object being referenced/de-referenced GetX() might just be ambiguous.

the answer to this is often “write good comments, and read the comments” but comments are not always a good answer either because we make arbitrary assumptions based on our own understanding in the moment we write the lines as to what is needed to be known.
Then many programmers will tend to write comments in their native language, or the primary language of the team working on it. Unreal is written by Prominently English speakers, a large majority of the user base are English Speakers, and even C++ many of the keywords of the language are in English (while reserving several special characters found in other spoken languages for specific meanings in the programming language, so they can not really be used in Variable names).
The Comments of Unreal Engine’s source code are in English meaning that there is a good chance of a spoken/reading language gap for “just read the comments”. I do not envy the situation of needing to pull out a translator/interpreter to read a singular comment to a function or line of code which are often incomplete sentences/thoughts that might not even map well to the other language, and this is if they are not just a bunch of words mashed together into a “descriptive name” (which I am sure a good portion of Translators/Interpreters just throw up all over)

for the example line of code you brought up:
ClockHandle->ResumClock(this, ClockHandle);

  • the first ClockHandle is to get the primary object context, though sometimes this is also done to get fast access to the class members regardless or the actual object the function is being called from, or to do specific work on or based on the object itself.
  • ResumeClock() is a member function of the class ClockHandle belongs to (the arrow operator tells us ClockHandle is a pointer, or at the very least is being treated as a pointer in this instance)
  • this” is a pointer (rarely is this* actually passed as a reference though it is possible) to the current object we are inside of, which is often used as a means of ownership, and/or callback if something needs to be done asynchronously or the receiving function may need to have access to the members of the caller. Where you called out this being a Blueprint node these often have a const UObject* WorldContext accepting a this* to ensure Garbage Collection doesn’t do unexpected or bad things in the middle, and to do things reserved to the things ‘knowing about’ the UWorld
  • The second ClockHandle which given that in the line was accessed as a pointer, and is not being de-referenced is being passed as a pointer to the function (we don’t get to see if there is any const in the function call, and it is really being passed as a *& which is a special way of passing an object that gets a bit technical), so this is either being used as a “resume from the value this once has” or “resume this one based on the one being called from”

passing a this* is not outlandish, and in Unreal C++ is rather common (especially for Blueprint nodes).

several of the function calls get away from “Clean Code” because of a near Generic implementation or they need to take in a lot of data, or even return a lot of data back through arguments passed by pointers, references, and Reference-to-Pointer. you also need to realize that many functions/events in blueprints are actually simplified for the user, like many blueprint users might be surprised to find PrintString() actually has 5 other arguments beyond the string itself, has no direct C++ equivalent, and when technically called in C++

// this is the fewest arguments this line of code gets.
GEngine->AddOnScreenDebugMessage( int32, float, FColor, FString)

while many of the functions used in blueprints will take a secret UObject* which just silently get called with this* behind the scenes (mainly to cut down of spaghetti noodles)

I don’t speak the language of the guide you are showing, so I am not sure if they make the point but: there are things that blueprints, and blueprint graph is more suited to doing

  • dealing with the Content Browser/Drawer to avoid Runtime-Resolved-Hard-String-References that can silently fail because something was renamed/moved or the spelling was off to begin with.
  • State Machines (animation and “AI” especially if you need fuzzy logic)
  • things dealing with timelines or absolute relative positioning

you can use the derived blueprint as a means for data capture into C++

1 Like

Thank you for your response.
I understand very well what you are saying!
(Also, I am now convinced that this implementation has such an interface because it is for Blueprint.)

And I apologize for the ambiguity of my question.
I did not properly convey what my concern was.
It was whether it is common to call such blueprint-specific interfaces from C++.
The proper question I should have asked is, “Why do some classes implemented in Unreal Engine (like Quartz in this case) only have Blueprint interfaces? Is it okay for me to call them from C++? Isn’t there a better way to call them?”

‘some classes only have blueprint interfaces’ is half true. the blueprint classes/nodes are defined/implemented in C++, much like in your own classes you can mark up a function with UPROPERTY(BlueprintCallable) to expose it to blueprints even defining what the pins of the node are displayed as.

when in reality most blueprint nodes are calls into C++ either the Object code binaries with BlueprintCallable, or assembly to run on a Script Virtual Machine with like BlueprintPure. designating your own BlueprintPure can save context switching from the Script Virtual Machine to the C++ Object Code binaries, and back.

the biggest difference is that by virtue of the reflection system blueprints have “technically” global scope with regards to includes, while in C++ we have to manually include headers to utilize types. when you include say “Kismet/KismetMathLibrary.h” for functions like sphere trace or FindLookAtRotation() are done using the C++ version of that function, while in blueprints it gets the compiled assembly going through the Script VM.

sometimes the simplifications done on these nodes just fill in the extra details for the script writer, while in C++ we need to manually instantiate like a TArray<FHitResult> so we can call LineTraceMulti___() and in blueprint that instantiation is done in the background. A big part of this is how strongly typed C++ is.

for if “common”, or “should”, the functions are there (as long as you do the includes), and you can call them if you want/need, and if you don’t like how one of the functions is done you can create your own version of it, and use that instead (in one project I disliked TQueue and ended up effectively making my own with stuff like PeekCurrent, PeekNext() or forcing the size to be consistent and wrapping a TArray), but you do that often enough and “why are you using the engine” which goes into what programming/Software Engineering is, and it can take quite a bit of time to get a working physics and rendering pipeline working and that is without making an editor.

I like to think of programming/Software-engineering as “glorified problem solving” where we are trying to make software to meet design goals/requirements, and sometimes that means “making something that works enough; To Heck with the repercussions,” then we go through bug fixing and optimizing once it works in the specific case.