UnrealC++でSlateのクラスをキャストする

#はじめに
[最近作っているプラグイン](https://github.com/Naotsun19B/GraphPrinter)でFSlateApplicationから取得したウィンドウからBlueprintやMaterialのグラフエディタのインスタンスを取得するために、Widgetの子を再帰的に辿って取得する処理が必要になり、そのためにSlateのクラス(`SWidget`を継承したクラス)をキャストする必要がありました。

※`SWidget`は`UObject`を継承していないため、標準のCast関数テンプレートは使えません。

#キャストする関数(マクロ？)
[GraphPrinterCore.h](https://github.com/Naotsun19B/GraphPrinter/blob/1a0303fb4da15bbb235e7df4afa3fd5433c25fec/Plugins/GraphPrinter/Source/GraphPrinter/Private/GraphPrinterCore.h#L94)

```GraphPrinterCore.h
namespace CastSlateWidgetPrivate
{
	template<class To, class From>
	TSharedPtr<To> CastSlateWidget(TSharedPtr<From> FromPtr, const FName& ToClassName)
	{
		static_assert(TIsDerivedFrom<From, SWidget>::IsDerived, "This implementation wasn't tested for a filter that isn't a child of SWidget.");
		static_assert(TIsDerivedFrom<To, SWidget>::IsDerived, "This implementation wasn't tested for a filter that isn't a child of SWidget.");

		if (FromPtr.IsValid())
		{
			if (FromPtr->GetType() == ToClassName)
			{
				return StaticCastSharedPtr<To>(FromPtr);
			}
		}

		return nullptr;
	}
}
#define CAST_SLATE_WIDGET(ToClass, FromPtr) CastSlateWidgetPrivate::CastSlateWidget<ToClass>(FromPtr, #ToClass)
```

`SWidget`は`UObject`ではないですが、自身のクラスの名前を保持しているためキャスト元の`SWidget::GetType`から取得した名前とキャスト先のクラス名を文字列化させたものを比較して、同一なら`StaticCastSharedPtr`でキャストします。

`StaticCastSharedPtr`はUObject以外(例えば構造体など)も`TSharedPtr`でラップすればキャストできる関数です。しかし、名前が`DynamicCastSharedPtr`でない所から分かる通り、もしキャストに失敗してもnullptrを返してくれません。そのため、クラス名の文字列での判定をしています。

``` Engine\Source\Runtime\SlateCore\Public\Widgets\DeclarativeSyntaxSupport.h
#define SNew( WidgetType, ... ) \
	MakeTDecl<WidgetType>( #WidgetType, __FILE__, __LINE__, RequiredArgs::MakeRequiredArgs(__VA_ARGS__) ) <<= TYPENAME_OUTSIDE_TEMPLATE WidgetType::FArguments()


#define SAssignNew( ExposeAs, WidgetType, ... ) \
	MakeTDecl<WidgetType>( #WidgetType, __FILE__, __LINE__, RequiredArgs::MakeRequiredArgs(__VA_ARGS__) ) . Expose( ExposeAs ) <<= TYPENAME_OUTSIDE_TEMPLATE WidgetType::FArguments()
```
余談ですが、軽く調べた感じだと`SWidget::GetType`から取得できる文字列はSlateを書く際に使う`SNew`のマクロ内で引数のクラス名を文字列化して変数に入れているようでした。
そのため、`UObject`系列のクラス名が接頭辞を抜いた形(例えば`UObject`なら「Object」)になっているのに対して、`SWidget::GetType`から取得できる名前は接頭辞付きなため、上記コードのようにマクロでキャスト先のクラス名をそのまま文字列化させています。

[GraphPrinterCore.cpp](https://github.com/Naotsun19B/GraphPrinter/blob/1a0303fb4da15bbb235e7df4afa3fd5433c25fec/Plugins/GraphPrinter/Source/GraphPrinter/Private/GraphPrinterCore.cpp#L107)

```GraphPrinterCore.cpp
TSharedPtr<SGraphEditor> FGraphPrinterCore::FindGraphEditor(TSharedPtr<SWidget> SearchTarget)
{
	TArray<TSharedPtr<SWidget>> ChildWidgets;
	CollectAllChildWidgets(SearchTarget, ChildWidgets);

	for (const auto& ChildWidget : ChildWidgets)
	{
		TSharedPtr<SGraphEditor> GraphEditor = CAST_SLATE_WIDGET(SGraphEditor, ChildWidget);
		if (GraphEditor.IsValid())
		{
			return GraphEditor;
		}
	}

	return nullptr;
}
```
使用する際はこのようになります。
こちらは**はじめに**でお話した子ウィジェットからグラフエディタを取得する処理です。

#おわりに
今回はウィンドウから子ウィジェットを走査する形は単純に重そうなので他の処理にするのも良いですが、この方法だと[こちら](https://qiita.com/EGJ-Ken_Kuwano/items/523c97697563751bcc71)で紹介されている`Widget Reflector`で確認できるWidgetに必ず辿り着ける点が魅力かと思われます。取得方法は後で考えるからとにかくこのWidgetに対して何かしたいなどの試作作業でも役に立つかもしれません。
そもそもSlateのクラスをキャストしようとすること自体あまりないかと思いますが、もしやろうとしている方の参考になれば幸いです。

明日は、@shiratori1221さんの「[GameplayAbilityとアニメーションモンタージュを使ったコンボについて](https://shiratori1221.hatenablog.com/entry/2020/12/15/000000)」です！