MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
TypeName.h
Go to the documentation of this file.
1/** @noop License Comment
2 * @file
3 * @copyright
4 * This Source Code is subject to the terms of the Mozilla Public License, v2.0.
5 * If a copy of the MPL was not distributed with this file You can obtain one at
6 * https://mozilla.org/MPL/2.0/
7 *
8 * @author David Mórász
9 * @date 2025
10 */
11
12/**
13 * @file
14 * Convert types to string
15 */
16
17#pragma once
18
19#include <string>
20
21#include "CoreMinimal.h"
22#include "UnrealCtre.h"
23#include "Mcro/Text.h"
24
25// TODO: C++ 20's std::source_location was used instead of this bouquet of macros, but then some platforms don't have <source_location>
26#if PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_MAC
27#define PRETTY_FUNC __PRETTY_FUNCTION__
28#elif PLATFORM_WINDOWS
29#define PRETTY_FUNC __FUNCSIG__
30#endif
31
32// disable this warning to allow us exploit division by zero error in consteval
33#pragma warning(push)
34#pragma warning(disable: 4804) // unsafe use of type 'bool' in operation
35
36/**
37 * Get a string view of the compile time typename
38 *
39 * @note
40 * Using std::(w)string here instead of FString or FStringView for consteval compatibility
41 *
42 * @tparam T target type
43 * @return string view of the name of the type
44 */
45template <typename T>
47{
48 using namespace Mcro::Text;
49 FStdStringView thisFunctionName { TEXT(PRETTY_FUNC) };
50
51#if PLATFORM_LINUX || PLATFORM_ANDROID || PLATFORM_MAC
52
53 // on Clang thisFunctionName looks like this:
54 // matching: [--------------------------------------------]
55 // | capturing: [-------------]|
56 // std::string_view GetCompileTimeTypeName() [T = MyTemplate<Vec>]
57
58 // on GCC thisFunctionName has a similar enough format so we can use the same pattern:
59 // matching: [-------------------------------------------------]
60 // | capturing: [-------------]|
61 // consteval std::string_view GetCompileTimeTypeName() [with T = MyTemplate<Vec>; std::string_view = std::basic_string_view<char>]
62
63 auto result = ctre::search<TEXT(R"(\s\[(?:with\s)?T\s=\s(?<TYPE>.+?)[;\]])")>(thisFunctionName);
64
65#elif PLATFORM_WINDOWS
66
67 // on MSVC thisFunctionName looks like this:
68 // matching: [----------------------------------------------------]
69 // | capturing: [---------------------]|
70 // class std::basic_string_view<char,struct std::char_traits<char> > __cdecl GetCompileTimeTypeName<struct MyTemplate<struct Vec>>(void)
71
72 auto result = ctre::search<TEXT(R"(GetCompileTimeTypeName<((struct|class)\s)?(?<TYPE>.+?)>\‍()")>(thisFunctionName);
73
74#endif
75
76 FStdStringView output = result.get<TEXT("TYPE")>().to_view();
77 size_t size = output.size();
78 size /= output.size() > 0; // Fail compilation with division-by-zero if we couldn't extract a typename
79
80 return size ? output : FStdStringView();
81}
82
83/** Convert types to string */
85{
86 using namespace Mcro::Text;
87
88 /**
89 * Get a friendly string of an input type without using `typeid(T).name()` Actually this method gives more
90 * predictable results across compilers than `typeid(T).name()` but still not 100% the same. Besides
91 * `TTypeName<T>` can deal with incomplete types as well, whereas `typeid(T)` cannot (more on this in
92 * remarks). While it is more consistent across compilers, it is still not 100% consistent. Take
93 * templated types for instance where MSVC compiler will add "class" prefix in front of type arguments
94 * where GCC or CLang might not. Because of subtle differences like this, it is strongly discouraged to
95 * assume the exact format of the output of `TTypeName<T>`
96 *
97 * Usage:
98 * @code
99 * FStringView myTypeName = TTypeName<FMyType>;
100 * @endcode
101 *
102 * It is useful in templates where a type name should be known in runtime as well. (e.g.: Modular features)
103 *
104 * @remarks
105 * Note on incomplete types: incomplete types on MSVC are localized to current scope. which means
106 * @code
107 * class I;
108 *
109 * class A
110 * {
111 * FStringView GetName() { return TTypeName<I>; }
112 * // Returns "I"
113 * }
114 * @endcode
115 * BUT:
116 * @code
117 * class A
118 * {
119 * FStringView GetName() { return TTypeName<class I>; }
120 * // Returns "A::GetName::I"
121 * }
122 * @endcode
123 * as you can see they are not equal. For sake of simplicity TTypeName only supports incomplete
124 * types when they're declared in global scope. Any other case might yield undesired results
125 * (e.g.: TTypeName<class I> of incomplete `I` might not be the same as TTypeName<I> somewhere
126 * else with `I` being fully defined)
127 *
128 * @tparam T The type to be named
129 */
130 template <typename T>
131 constexpr FStringView TTypeName = FStringView(
132 GetCompileTimeTypeName<std::decay_t<T>>().data(),
133 GetCompileTimeTypeName<std::decay_t<T>>().size()
134 );
135
136 /** @see TTypeName */
137 template <typename T>
139
140 /** @see TTypeName */
141 template <typename T>
142 const FName TTypeFName = FName(TTypeName<T>.Len(), TTypeName<T>.GetData());
143
144 /** @see TTypeName */
145 template <typename T>
146 const FString TTypeString = FString(TTypeName<T>.GetData(), TTypeName<T>.Len());
147}
148
149#pragma warning(pop)
consteval Mcro::Text::FStdStringView GetCompileTimeTypeName()
Definition TypeName.h:46
std::wstring_view FStdStringView
Definition Text.h:31
const FString TTypeString
Definition TypeName.h:146
constexpr FStringView TTypeName
Definition TypeName.h:131
const FName TTypeFName
Definition TypeName.h:142
constexpr FStdStringView TTypeNameStd
Definition TypeName.h:138