The asterisks in UE_LOG, *Name and *OwnerLocationString, are CONFUSING

For anyone looking for an explanation as to why we should need to dereference an FString - which is not a pointer - don’t worry… You’re not crazy. The course is just INCREDIBLY MISLEADING at this part.

In the documentation for FString, you’ll find this note, “When using %s parameters to include FStrings, the * operator must be used to return the TCHAR required for the %s parameter.*”

So, what’s going on here (I think) is that an ancient implementation of string handling from the days of C (before there was even a # or ++) is used when you embed your string variables with %s. The string variable we want to print is stored in memory (more or less) as an array of CHAR’s.

[“H”,“e”,“l”,“l”,“o”," ",“W”,“o”,“r”,“l”,“d”,(END)]

So, the asterisk in *name points to the location in memory where this array of CHAR’s begins, allowing the TEXT() call to hop there when it adds to the explicit string.

But because this is the example used as the introduction to pointers - and because this extra asterisk is not explained - the student reasonably ends up thinking that they are dereferencing something they shouldn’t have to dereference. “I didn’t make an FString* pointer, I made an FString!” That is the correct and logical response. And you are right! Especially in lieu of how EXPLICIT C++ is everywhere else.

The asterisk used in this UE_LOG call is dereferencing an implicit array of CHARs behind the FStrings you are actually working with, because that’s just how the TEXT() call and %s works, apparently.

The course really should pause to note this. Not only to avoid the reasonable confusion, but to give another example of how pointers are used in the language.

1 Like

It isn’t; it’s dereferencing the FString, it doesn’t dereference anything else. You can overload operators in C++ including the * operator, you can have it do whatever you want. Even something nonsensical like this

Compiler Explorer


Though yes that the reason you need to use that overloaded operator is because of C. It is using one of the printf family of functions from the C Standard Library to do the formatting. As it’s from C it can only handle built-in types, char, int, etc. (though not bool as that wasn’t an actual type in C until quite recently).

The way C does strings is will a null terminated string of characters, that is each character is next to each other in memory ending in the null character to denote the end, then C API’s would take a pointer or a pointer and size i.e.

some_c_func(char* str);
some_c_func(char* str, int count);

and would either read until count characters are read or the null character was found.

In string C++ classes, they would be internally storing that (though they need not end in a null character unless they want to interface with C API’s that don’t have a version to pass the size). To interface with C API’s string classes tend to have a way to get a pointer to the first character it will be storing. The string class in the C++ Standard Library has one called data(), (it also has one called c_str() as before C++11 it wasn’t required to be null-terminated so that function would add one if it didn’t end with one)

So in standard C++ this is what is happening:

Compiler Explorer

Note that there’s no dereferencing, the call to data() is equivalent to *FString.

As an aside C++23 adds std::print and std::println so you can do away with the old ways and do Compiler Explorer

So, is the end result here, that a pointer for the first character in the FString gets handed to the TEXT() call? That’s how it read to me after I had to go digging – because nothing was said of it in the course. And that’s why I described it as an array of characters in memory, “more or less.” Even threw in a terminator character in my array-illustration. :wink:

I saw your previous response to the other fellow who brought this up, before posting, too. But the operand-overloader description you gave there didn’t really meet needs. I think you’ve done a much better job here. That said, I found conceptualizing the important bit of what was going on was much easier once I had that TCHAR* reference at hand from the documentation.

All that aside, you can surely agree that in a 101 on pointers, failing to hang a lampshade on the OTHER thing in those 5 lines of code that looks like a pointer, and ultimately functions as a TCHAR* pointer internally, lays land mines of confusion for students taking the course.

Now, I wouldn’t suggest trying to explain operand overloading, just to get to the other side of a pointers intro, but the instructor has to say something about that asterisk. Any unfamiliar applying basic logic to the use of “these funny little asterisks” in c++ is going to see that and realize it doesn’t make sense, but be left with no explanation… Or worse, they’ll come up with some way to justify it in their head that will leave them confident, but confused, about the most dangerous core feature of the language.

[I DELETED my first attempt at this post and RESTATED it the way I actually *mean,* before receiving a reply. I was kind of loose with terms in the first pass of writing it, yesterday. And I’ve tried to be more exacting in my language.]


How is it dereferencing the FString, when the FString wasn’t an FString*. It was not invoked as a pointer. What I’m thinking you mean, at this point, is that the asterisk in **Name is overloaded to pass a reference to the first TCHAR pointer of the actual string data. Because that’s what UE’s documentation says the UE_LOG’s %s needs when invoked.

To my mind, dereferencing an FString needs a pointer to an FString - not the FString as an object. And dereferencing an FString pointer would return, ya know, the FString as an object! Whereas, because the documentation says the asterisk is invoked to pass the TCHAR* pointer, (pictured in the first post) and because you say the dereferencing operand has been overloaded, I conclude the asterisk has been overloaded to pass that pointer of the first TCHAR in the string set.

Which is a complete inversion of how the dereference operator is used in c++ dereferencing. Instead of resolving the memory location indicated by a pointer to its object, and accessing the object… The dereferencing operand is here being used to produce a pointer reference from an object! The UE_LOG function has us dereferencing an object… and getting back a pointer!?

If true, that is conceptually upside down. And that’s why something needs said about it, in this chapter. Because left unmentioned, it leaves the unfamiliar student with an example of dereferencing that is utterly nonsensical.

At least… This is my understanding of it, right this very second: Maybe, with this - less sloppy - explanation of my take, you can tell me if I am still completely lost in the woods, here. :slight_smile:

Because at this point, it seems like your explanation, and the documentation itself, are at odds. And I’ve run this by another c++ coder in my community, who is - if nothing else - equally baffled by the documentation’s implied dereference of an object, that returns a pointer.

[panting]

To be honest I don’t think there is a great way to cover this. I think the best way for an introductory Unreal course (which this is) is for a handwavey “you have to use * to log FStrings for legacy reasons” and note that the FString isn’t a pointer and pretty much leave it at that; maybe add on that it’s called operator overloading.

Going into more detail is most likely going to confuse or alienate a lot of people and I’m not quite sure what the benefit would be. You don’t need to know about C-style strings, case in point Unreal now has proper formatted logging. If the course were to be redone today it could change all logs to use that and completely side step the issue.

include "Logging/StructuredLog.h"

FString Name = TEXT("Dan");
// doesn't even need the TEXT macro, it'll handle that for you
UE_LOGFMT(LogTemp, Warning, "My name is {0}", Name) 

With that change to the course you could go through the entire process of making a game without ever knowing about C-strings, so other than general computer science knowledge I don’t think it would add anything.


Because it’s using the dereference operator (*), as you can see, you can dereference more than just pointers.

I do not, * is called the dereference operator. You’re using it on an FString so you are dereferencing it.

You can have any overloadable operator do whatever you want. Though you should only do so when it actually makes sense like adding vectors together.

It’s common for strings to have + for concatenation. It may seem weird to some as there’s no notion of mathematically adding strings. Though a lot of people are fine with it as it “makes sense”.

FString using * is IMO not great. Dereferencing a pointer means getting the value at the address, so I would expect overloaded ones to do something similar, to use the standard library as an example here are 3 types that overload it

std::unique_ptr<T>
std::list<T>::iterator
std::optional<T>

All three of these types overload the * operator.

  1. uses it to get the value at the address, perfect use.
  2. iterators are generalisations of pointers where they “point” to an element within a container. A std::list iterator isn’t going to be a pointer itself (but it will contain a pointer to a node) and dereferencing would mean to get the value of the element.
  3. An optional either contains a value or it doesn’t. To get the value out of the optional you can use the * where if it contains a value you will get it, if it doesn’t an exception will be thrown.

So FString’s use of it doesn’t really align with any of those, it’s sort of close to the optional case but not quite, it’s “getting the value” but that’s more of an implementation detail than anything.

IMO I think it’s a misuse of operator overloading and as stated previously, would prefer if it were a function.


Basic string class to show what’s going on: Compiler Explorer

1 Like

Yeah. I think we’re completely on the same page. I even think we’ve just been saying the same thing back and forth at each other the whole time – but from different perspectives and with different language. :rofl:

I like your “legacy reasons” statement as a proposal for glossing passed it in the course. And even better if the issue could have been dodged entirely with a more modern logging function. Lessons for next time!

As there’s little to be done about its presence in the video chapter now… Should another student finds us talking about it here, after scratching their head, as well… Here’s my final statement:

Overloading the * dereferencing operator in this way was a bad implementation on Unreal’s part. Convenient, once you know you need it. But confusing, unintuitive, and poorly documented, when you don’t understand why its there. You were right to be confused. It’s confusing. Mind the gap, and carry on. :slight_smile:

1 Like

Thank you for this thread. Google brought me here. I was confused as well. I agree that just MENTIONING it and just describe it as a “special case” would be enough to limit confusion. I kept thinking I missed something and re-watched it. Course has been great up to this point, but this was frustrating.

1 Like

Privacy & Terms