How to make a getter for a C++ TMap return a reference so that the TMap can be modified in BP?

Hi,
I have the following member variable:

UPROPERTY(Config, EditAnywhere, BlueprintReadWrite)
TMap<FString, float> VolumeMap;

If I make it public and then in BP I simply use GetVolumeMap node, then call e.g. Add or Clear on it, an entry is added or the map is cleared as expected (as you can see it’s a Config var, so the changes are indeed save to the config file).

However, instead of making this map variable public, I wanted to make it protected/private and create a public getter for, but such that it would return it by reference, so that I can still use things like Add or Clear on it in BP.

This is what I did:

UFUNCTION(BlueprintPure, Category=Settings) TMap<FString, float>& GetVolumeSettingsDict();
TMap<FString, float>& UMyGameUserSettings::GetVolumeSettingsDict()
{
    return VolumeMap;
}

However, when I use this function in BP, it does return the map, but Add or Clear doesn’t change its contents (I checked it by debugging in BP - the content of the map doesn’t change, and obviously no changes are made to the config file).

What am I doing wrong here?

When you call GetVolumeSettingsDict() in BP, it doesn’t return a reference to the map but copies it. You can’t return a pointer either, because UFUNCTION only supports returning UObject*.

one of the big weaknesses of BP, the only solution ive found (aside from a direct ref) is to wrap a pointer to the map in a struct and edit it that way.

@OctoberIC Wow, that’s incredibly weird. I mean, in c++ the function clearly states that it returns a ref, so I’m surprised this is actually even possible.
@Auran131 could you please show me an example of how to do that? I don’t think I understand it.

i can if you’d like but i think its overkill for your situation. really your TMap should be private anyway and you should create add/remove/get functions in your class or even use an interface

1 Like

This is the way. Maybe you can do some UPROPERTY kungfu to have a ref to the map be readable by BPs.. But a much cleaner/simpler way is to have operation functions that you will call from BP.

I’ve probably forgotten a lot of things about config properties, but if I recall correctly, you would need to do some kind of saving operation to persist these changes into a file.. That’s when these operation functions not only are cleaner, but also useful to you! In order to trigger the saving process! (If I’m not mistaken)

At this point you probably looking for AllowPrivateAccess:

private:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
    int32 MyPrivateInt;

This way variable will be kept as private in c++, while still be considered “public” and bp-accessible as usually.
And if you really need a dedicated getter to make some work along with returning, them i’m afraid you out of options. Of course, you always may write your own add, remove, at, etc that will access the required variable directly

As for why your getter doesn’t work - UE gives some nice tools like bp-compatible reflection and that comes with some limitations. In particular, afaik, your getter got wrapped into bp-adapter that indeed receives ref to your map, copy it and pass it further to blueprints.

Thank you all for your suggestions. I’m not marking any answer as the solution, as I don’t really think there is a solution to what I wanted to do.

It’s a shame really, as all of the suggested workaround have drawbacks. I simply find it weird that Unreal doesn’t allow BP to get a reference. What’s more, it is misleading, as it should throw some error/warning that returning a ref isn’t possible if function is ‘Blueprintcallable’ - the way it is now, is that my c++ function actually does return a ref, but in BP it returns a copy, which basically means it’s a different function and you get no information about it.

So, to sum it up, these are the possible solutions (or workarounds, as solution doesn’t exist):

  1. make the TMap variable public (probably the best option)
  2. make the TMap variable private, and create add/remove/get functions (obviously problematic as you rewrite functions that are already there - not to mention that, if I understand correctly, the get function will still only return a copy anyway, so you won’t be able to modify that value, unless you add it to the TMap again. And you need to do that for every single TMap in every single class…)
  3. wrap a pointer to the TMap in a struct (not sure how to do that, though)
  4. use meta = (AllowPrivateAccess) to keep it private in C++, but public in BP - I don’t see any reason to do that, though. Either I need the var to be private or public, making it private in C++ and public in BP seems to make it confusing

the reason this is the usual solution is you shouldn’t really be modifying properties from other classes anyway.

for instance in your original post you have say a setting menu, why would any other class access the TMap over a function that simple calls AddVolume? one of the other reasons you wrap that in a function is you probably want to clamp the volume before setting it.

Yeah, the thing is, we’re talking about the C++ vs BP class. So, theoretically BP version is a different class, but in reality it’s just like an extension of the C++ version in Blueprints, to make certain things easier to modify. That depends on the context I guess, but more often than not, I treat the BP version just like the same class, only accessible from BP directly, not like any other class. And that’s precisely what it is in this case - I could theoretically make everything in C++, but for some reason Blueprints exist (I’m not a fan of them, to say the least, precisely of limitations such as these - among other things - but they are indeed more convenient in some ways).

Overall, I get where you’re coming from with suggesting separate add/remove functions - right now it’s not that I absolutely need to have this getter with a reference, it’s more about that I’m simply surprised that BP work that way, especially since this getter compiles without any warning or other kind of info stating that “making a Blueprintcallable function return a reference is not possible” or something along these lines. Right now it’s just confusing, as this function seems to be working, but in reality it does not.