UnrealC++で構造体(USTURCT)をキャストしてみる


はじめに

UnrealC++には「Unreal Property System」というリフレクションシステムがあります。
アンリアルのプロパティ システム (リフレクション)
リフレクションとは実行時にプログラムの構造(クラスの継承関係やどんな関数や変数を持っているかなど...)取得したり設定したりすることのできる仕組みです。
リフレクション (情報工学)
標準のC++には今の所リフレクション機能はありませんが、UnrealC++では独自のリフレクションシステムが組み込まれているため、文字列からクラスや関数を取得したり、Cast関数を使ってクラスをキャストしたりできます。

構造体ををCast関数でキャストしてみる

TestStructs.h
USTRUCT(BlueprintType)
struct FTestStructBase
{
    GENERATED_BODY()
};

USTRUCT(BlueprintType)
struct FTestStructA : public FTestStructBase
{
    GENERATED_BODY()

public:
    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    int32 TestInteger;
};

USTRUCT(BlueprintType)
struct FTestStructB : public FTestStructBase
{
    GENERATED_BODY()

public:
    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FString TestString;
};

USTRUCT(BlueprintType)
struct FTestStructC : public FTestStructBase
{
    GENERATED_BODY()

public:
    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FVector TestVector;
};

まずはベースとなる構造体とその派生を3つ定義してみます。

TestFunctionLibrary.cpp
void TestFunction(const FTestStructBase& TestStruct)
{
    if (FTestStructA* StructA = Cast<FTestStructA>(&TestStruct))
    {

    }
}

このように通常のUClassの様にCastをしようとすると以下のコンパイルエラーが発生します。

'To *TCastImpl<From,To,ECastType::UObjectToUObject>::DoCast(UObject *)': cannot convert argument 1 from 'From *' to 'UObject *'

Cast関数を使用できるのはUObjectを継承したクラスのようです。当然UStructではありますが、構造体自体はUObjectを継承していないためこの関数は使えません。

構造体をdynamic_castでキャストしてみる

Cast関数が使えなくてもC++には「dynamic_cast」がある!ということでやってみます。

どうやらマクロに置き換わってしまい普通のdynamic_castは使えないようです。
何に置き換えられるかというと「UE4Casts_Private::DynamicCast」という関数に置き換わるようです。

Engine\Source\Runtime\CoreUObject\Public\Templates\Casts.h(476)
#define dynamic_cast UE4Casts_Private::DynamicCast

4種類ほどDynamicCast関数が定義されていますが、なんとなく使ってほしくなさそうな雰囲気...
そしてやはり使えませんでした。

'dynamic_cast': 'FTestStructBase' is not a polymorphic type

構造体をstatic_castでキャストしてみる

奥の手である「static_cast」を使って...

TestFunctionLibrary.cpp
void TestFunction(const FTestStructBase& TestStruct)
{
    if (FTestStructA* StructA = static_cast<FTestStructA*>(&TestStruct))
    {

    }
}

static_castはポインタ型を他のポインタ型に強制的に変換するためTestStruct変数がFTestStructAの型でない場合でもStructAがnullptrにはなりません。

UScriptStruct::GetStructCPPNameで取得した名前を比較する

USTRUCTで定義した構造体にはStaticStruct関数が含まれていて、この関数でUScriptStructが取得できます。
そしてGetStructCPPName関数からは関数名の通り構造体の名前が取得できます。

※追記 GetStructCPPNameで取れる名前はGetNameで取れる構造体名に接頭辞のFが付いただけなのでGetNameでも問題ありません。

この方法では以下の様に引数や変数定義で型が決まっていると親の構造体の名前が取得されてしまうため使えません。

TestFunctionLibrary.cpp
void TestFunction(FTestStructBase TestStruct)
{
    if (UScriptStruct* ScriptStruct = InTestStruct.StaticStruct())
    {
        if (ScriptStruct->GetStructCPPName() == FTestStructA::StaticStruct()->GetStructCPPName())
        {

        }
    }
}

しかし、以下の様にプロパティの走査で指定の構造体かを判定する場合には使えます。

TestFunctionLibrary.cpp
void TestFunction(UObject* Owner)
{
    for (TFieldIterator<FProperty> PropertyItr(Owner); PropertyItr; ++PropertyItr)
    {
        FProperty* Property = *PropertyItr;

        if (auto StructProperty = CastField<FStructProperty>(Property))
        {
            if (auto ScriptStruct = StructProperty->Struct)
            {
                if (ScriptStruct->GetStructCPPName() == FTestStructA::StaticStruct()->GetStructCPPName())
                {

                }
            }
        }
    }
}

おわりに

色々と試した結果あまり良い方法は見つかりませんでした...
継承して使用するのであれば構造体ではなくUObjectを継承して使うのが良さそうです。
どうしても構造体でやるのであれば独自に固有のIDのようなものを持たせてそれを元に判定するなどの工夫が必要そうです。

なにか良い方法をご存知でしたらコメントなどで教えて頂けますと幸いです。