TUnion issues

First issue relates to this report here. Even though that has now been corrected, I am still receiving an unresolved external symbol when linking a module that uses TUnion. I believe the reason is that the log category is not being exported.

TUnion code invokes UE_LOG in its header, which is not something that is generally done. This means any module including Union.h will include that code and attempt to use the log category. Since the declaration is not marked for dll export, you get a linker error. I believe prefixing the declaration with CORE_API should fix this:

CORE_API DECLARE_LOG_CATEGORY_EXTERN(LogUnion, Log, All);

I’d be interested to know if Epic make use of TUnion at all. It doesn’t show up as being used in the Github codebase as far as I can see. The reason is that even after using the workaround in the linked question for the above issue, I’m getting an obscure runtime exception inside FString code when using the union. I substituted boost::variant for TUnion and everything works as expected, leading me to suspect there are further issues with the TUnion implementation.

Hi Kamrann,

We seem to use TUnion in only one place: PhysXCollision.h.

TUnion has some issues - like a limit of 6 types, poor (zero) compile-time handling of repeated types and no assignment operator. I would like us to have a proper TVariant some day, though it seems there is little demand for it right now.

For now, I would recommend that you use boost::variant with the following caveat: it may or may not be trivially relocatable, which all UE4 types are expected to be. This means that you could have trouble mixing Boost types and UE4 types - particularly our containers, i.e.:

TArray<FTypeContainingABoostVariant> DisasterWaitingToHappen;

If this is a problem for you, you could wrap your variant in a TUniqueObj as a workaround:

struct FTypeContainingABoostVariant
{
    TUniqueObj<boost::variant<FTypeA, FTypeB, FTypeC>> Variant;
};

Hope this helps,

Steve

Hi Steve, thanks for the info.

Yep, I’m surprised at how little it seems to be used, in the engine or by others. I suppose it’s because for the most part it’s an optimization, and the same functionality can be achieved either with a raw C++ union, or in a less memory-efficient way by just stacking members in a struct. I agree it would be good if the engine provided an updated variant type, utilizing variadics to overcome the current 6 type limit.

The other issue with using boost is that even trying to extract only the dependencies of boost::variant results in a pretty huge list of boost headers. Since this relates to a marketplace project I’m working on, I’d rather not have to add this dependency, especially since it may reduce platform/compiler compatibility. Anyway, I’ll stick with this solution for now.

I’m currently using the boost::variant, stored by value (indirectly through a few structs) within a TArray, and have not noticed any issues. Are you saying this is inherently unsafe? I’m not clear on exactly what you meant by ‘trivially relocatable’. Perhaps this is only an issue if one of the types in the variant is not movable?

When I say ‘relocatable’, I mean moving an object from one memory location to another and destroying the original. What I say ‘trivial relocatable’, I mean that this can be done via memcpy. So, in other words, UE4 assumes that these are equivalent:

template <typename T>
void Relocate(void* Dest, T& Src)
{
    new (Dest) T(MoveTemp(Src));
    Src.~T();
}

template <typename T>
void TriviallyRelocate(void* Dest, T& Src)
{
    FMemory::Memcpy(Dest, (void*)&Src, sizeof(T));
}

This is true for the vast majority of types (e.g. types which wrap pointers are typically not trivially copyable nor trivially movable, but are trivially relocatable). However, types which rely on pointers to their own members or which use the ‘this’ pointer as a form of identity are not. For example, std::string isn’t usually trivially relocatable because, if it has a small string optimisation, the string object may point to one of its own members which it uses as storage.

I’ve had a quick look at the current implementation of boost::variant and it appears as though it’s safe (assuming the types you want to store in it are also trivially relocatable), though I obviously cannot guarantee that this will always be the case across different Boost versions or build configs. As I say, I can only guarantee that it will always be safe if you wrap it up in something like TUniqueObj.

Steve

Great explanation, really appreciate it. I’ll add the TUniqueObj wrapper to be on the safe side.
Thanks Steve.