5 minute read

Warning: This blog post assumes you have intermediate to advanced knowledge of C++. Misuse of the CPP macro may result in unpredictable behaviour.

Danger: It is critical that you understand the implications of this. UHT will only generate code for what it knows, which can potentially lead to undefined or unpredictable behaviour. Use at your own risk.

Intro

From time to time, you want to expose some class or struct to the engine, likely for usage in Blueprints. While this is one of the many awesome features of the Unreal Engine, it comes with some drawbacks:

  • Anything you expose to Blueprints must be known to the Unreal Header Tool (UHT).
  • UHT will not recognise anything not exposed to it, so inheriting from any class or struct outside of the UE ecosystem is impossible.

This leads to developers forcing themselves to make an API a bit more convoluted that it needs to in exchange for Blueprint compatibility. A good example of this are unions, which the engine does not support if you want to expose them to Blueprints.

What if I told you that you could hide some parts of your code from UHT, giving you the best of both worlds?

Introducing, the CPP macro

The CPP macro is used extensively in the engine and Epic’s own code, however it lacks documentation (unsurprisingly). It is defined either as 1 or 0, making it mostly useful for conditional compilation using the #if preprocessor directive.

// CoreDefines.h
/*----------------------------------------------------------------------------
Metadata macros.
----------------------------------------------------------------------------*/

#define CPP       1

When compiling code, UHT runs first, generating code using the engine’s macros we all know and love (UCLASS(), USTRUCT(), GENERATED_BODY() etc.). When UHT’s header parser encounters an #if CPP statement, it will completely ignore anything that comes after. As such, you can easily hide code from UHT with the following:

#if CPP
// Anything in here will be completely ignored by UHT.
#endif

Info: UHT is able to parse both #if CPP and #if !CPP, as seen in HeaderParser.cpp (UE4) or UhtHeaderParser.cs (UE5). Many thanks to DJ Hirko for the information.

Deriving from non-UHT types

One of the most frequent uses of the CPP macro I encounter is needing a USTRUCT() to derive from a type that isn’t compatible with UHT.

This ranges from simple structures containing POD to templated code. Attempting to derive from these will result in one of the following errors:

  • Unable to find parent struct type for ‘FMyStruct’ named ‘SomeStructUnknownFromUHT’
    • You tried to derive from a class or struct unknown to UHT.
  • Found ‘<’ when expecting ‘{‘ while parsing struct ‘FMyStruct’
    • You tried to derive from a templated type.

However, there will be times where you need your struct to be exposed to Blueprints anyway.

Wrapper Structs

A common solution is to wrap your struct in another that will only contain the member:

struct FMyStruct : public TSomeTemplate<FMyStruct>
{
    // Some code that UHT likes even less than you deriving from a template.
};

USTRUCT(BlueprintType)
struct FMyStructWrapper
{
    GENERATED_BODY()

    FMyStructWrapper()
        : UnderlyingStruct()
    {}

    // Wraps around the underlying struct so you can pass it around in Blueprints.
    FMyStruct UnderlyingStruct;
}

Now the downside of this is that you’ll also need to make blueprint function libraries to interact with FMyStructWrapper’s underlying struct. This is time consuming and bloats your API. Let’s see the alternative.

Using the CPP macro

Using the CPP macro, we can have FMyStruct derive from a templated type without the UHT knowing about it:

USTRUCT(BlueprintType)
struct FMyStruct
#if CPP
    : public TSomeTemplate<FMyStruct>
#endif
{
    GENERATED_BODY()

    FMyStruct()
    {}

    // ...
};

This piece of code allows FMyStruct to derive from a templated type and to be exposed to Blueprints. The reason this compiles is because UHT ignores anything marked with the #if CPP preprocessor directive, and as such, does not know about the struct it derives from.

While Blueprints will not have anything relating to TSomeTemplate<FMyStruct>, your native code can continue to work as before.

Exposing unions to Blueprints

Another possibility the CPP macro gives us is to expose the fields of a union to Blueprints.

Let’s write an example struct containing a union:

USTRUCT(BlueprintType)
struct FPackedBooleans
{
    GENERATED_BODY()

    union
    {
        struct
        {
            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue0 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue1 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue2 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue3 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue4 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue5 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue6 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue7 : 1;
        };

        uint8 PackedBits;
    };
};

Our FPackedBooleans struct contains a bitfield of 8 values that can easily be retrieved as a single byte (PackedBits) that holds them all. However, should you try to compile this, you will quickly find out that UHT completely ignored the fields inside the union.

Attempting to make an FPackedBooleans struct in a Blueprint, you’ll see that you cannot split it. Furthermore, attempting to set members in the struct reveals that it has no members at all.

An image showing that the "Set members in PackedBooleans" node has no members to modify

Info: It seems like UHT ignores unions when parsing classes and structs exposed to it.

However, we can easily fix this, giving us the desired behaviour on the Blueprint side of things:

USTRUCT(BlueprintType)
struct FPackedBooleans
{
    GENERATED_BODY()

#if CPP
    union
    {
        struct
        {
#endif
            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue0 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue1 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue2 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue3 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue4 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue5 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue6 : 1;

            UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint8 bValue7 : 1;
#if CPP
        };

        uint8 PackedBits;
    };
#endif
};

While this isn’t the prettiest code in the world, it resolves our problem. For all intents and purposes, UHT sees FPackedBooleans as a simple struct with 8 bitfield members, which it will consider booleans:

// Simple representation of what the Unreal Header Tool "sees" thanks to the CPP macro.
USTRUCT(BlueprintType)
struct FPackedBooleans
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue0 : 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue1 : 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue2 : 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue3 : 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue4 : 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue5 : 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue6 : 1;

    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    uint8 bValue7 : 1;
};

If we recompile our code and look at a Blueprint again, we see that UHT now sees our 8 members properly.

An image showing that the "Set members in PackedBooleans" node has all members.

And by using the CPP macro, our C++ code remains the same and is unaffected.

Conclusion

Using the CPP macro allows developers to maintain a clean API even when exposing it to Blueprints. While it isn’t a bandaid fix for every scenario, it handles quite a few, and is a great asset for any toolbelt.