MCRO
C++23 utilities for Unreal Engine.
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages Concepts
Conversion.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#pragma once
14
15#include <ranges>
16
17#include "CoreMinimal.h"
18
19#include "Mcro/Concepts.h"
22#include "Mcro/Templates.h"
23#include "Mcro/Text.h"
24
26#include "range/v3/all.hpp"
28
29namespace Mcro::Range
30{
31 using namespace Mcro::Templates;
32 using namespace Mcro::Concepts;
33
35 {
36 FString Start { TEXT_"[" };
37 FString End { TEXT_"]" };
38 FString Separator { TEXT_", " };
39 };
40
41 namespace Detail
42 {
43 template <CRangeMember Range>
45 {
46 TRangeWithStringFormat(Range const& range, FRangeStringFormatOptions const& options)
47 : Storage(range)
48 , Options(options)
49 {}
50
51 auto begin() const { return Storage.begin(); }
52 auto end() const { return Storage.end(); }
53
55 private:
56 Range const& Storage;
57 };
58 }
59
60 /** @brief Specify a separator sequence for a range when converting it to a string */
61 FORCEINLINE auto Separator(FString const& separator)
62 {
63 return ranges::make_pipeable([separator] <CRangeMember Input> (Input&& range)
64 {
65 if constexpr (CIsTemplate<Input, Detail::TRangeWithStringFormat>)
66 {
67 range.Options.Separator = separator;
68 return range;
69 }
70 else return Detail::TRangeWithStringFormat(range, {.Separator = separator});
71 });
72 }
73
74 /** @brief Specify a start and an end sequence enclosing this range when converting it to a string */
75 FORCEINLINE auto Enclosure(FString const& start, FString const& end)
76 {
77 return ranges::make_pipeable([=] <CRangeMember Input> (Input&& range)
78 {
79 if constexpr (CIsTemplate<Input, Detail::TRangeWithStringFormat>)
80 {
81 range.Options.Start = start;
82 range.Options.End = end;
83 return range;
84 }
85 else return Detail::TRangeWithStringFormat(range, {.Start = start, .End = end});
86 });
87 };
88
89 /** @brief Don't use a separator when this range is rendered to a string */
90 FORCEINLINE auto NoSeparator()
91 {
92 return ranges::make_pipeable([] <CRangeMember Input> (Input&& range)
93 {
94 if constexpr (CIsTemplate<Input, Detail::TRangeWithStringFormat>)
95 {
96 range.Options.Separator = {};
97 return range;
98 }
99 else return Detail::TRangeWithStringFormat(range, {.Separator = {}});
100 });
101 }
102
103 /** @brief Don't enclose this range in anything when it's rendered to a string */
104 FORCEINLINE auto NoEnclosure()
105 {
106 return ranges::make_pipeable([] <CRangeMember Input> (Input&& range)
107 {
108 if constexpr (CIsTemplate<Input, Detail::TRangeWithStringFormat>)
109 {
110 range.Options.Start = {};
111 range.Options.End = {};
112 return range;
113 }
114 else return Detail::TRangeWithStringFormat(range, {.Start = {}, .End = {}});
115 });
116 }
117
118 /**
119 * @brief
120 * Don't insert anything else than the contents of the input range when that is rendered as a string just append
121 * each item one after the other.
122 */
123 FORCEINLINE auto NoDecorators()
124 {
125 return ranges::make_pipeable([] <CRangeMember Input> (Input&& range)
126 {
127 if constexpr (CIsTemplate<Input, Detail::TRangeWithStringFormat>)
128 {
129 range.Options = {{}, {}, {}};
130 return range;
131 }
132 else return Detail::TRangeWithStringFormat(range, {{}, {}, {}});
133 });
134 }
135
136 namespace Detail
137 {
138 template <typename CharType>
139 void CopyCharactersToBuffer(CharType const& value, int32 chunks, int32& position, TArray<CharType>& buffer)
140 {
141 if (buffer.Num() == position) buffer.AddZeroed(chunks);
142 buffer[position] = value;
143 ++position;
144 }
145
146 template <CStringOrView String>
147 void CopyStringToBufferUnsafe(String const& value, int32& position, TArray<TCHAR>& buffer)
148 {
149 FMemory::Memcpy(buffer.GetData() + position, GetData(value), value.Len() * sizeof(TCHAR));
150 position += value.Len();
151 }
152
153 template <CStringOrView String>
154 void CopyStringItemToBuffer(String const& value, FString const& separator, int32 chunks, int32& position, TArray<TCHAR>& buffer)
155 {
156 int nextLength = value.Len() + separator.Len();
157
158 if (buffer.Num() <= position + nextLength) buffer.AddZeroed(chunks);
159 if (!separator.IsEmpty())
160 CopyStringToBufferUnsafe(separator, position, buffer);
161 CopyStringToBufferUnsafe(value, position, buffer);
162 }
163 }
164
165 /**
166 * @brief Render an input range as a string.
167 *
168 * For ranges of any char type, the output is an uninterrupted string of them. Other char types than TCHAR will
169 * be converted to the encoding of the current TCHAR.
170 *
171 * For ranges of strings and string-views individual items will be directly copy-appended to the output separated
172 * by `, ` (unless another separator sequence is set via `Separator`)
173 *
174 * For anything else, `Mcro::Text::AsString` is used. In fact this function serves as the basis for `AsString` for
175 * any range type. Like for strings, any other type is separated by `, ` (unless another separator sequence is set
176 * via `Separator`). For convenience a piped version is also provided of this function.
177 */
178 template <CRangeMember Range>
179 FString RenderAsString(Range&& range)
180 {
181 using ElementType = TRangeElementType<Range>;
182
183 if (IteratorEquals(range.begin(), range.end()))
184 return {};
185
186 constexpr int chunks = 16384;
187 int32 position = 0;
188 FRangeStringFormatOptions rangeFormatOptions;
189
190 if constexpr (CIsTemplate<Range, Detail::TRangeWithStringFormat>)
191 rangeFormatOptions = range.Options;
192
193 if constexpr (CChar<ElementType>)
194 {
195 TArray<ElementType> buffer;
196 for (ElementType const& character : range)
197 Detail::CopyCharactersToBuffer(character, chunks, position, buffer);
198
199 if constexpr (CCurrentChar<ElementType>)
200 return FString::ConstructFromPtrSize(buffer.GetData(), position);
201 else
202 {
203 TStdStringView<ElementType> stringView(buffer.GetData(), position);
204 return UnrealConvert(stringView);
205 }
206 }
207 else if constexpr (CStringOrView<ElementType>)
208 {
209 TArray<TCHAR> buffer;
210 for (auto it = range.begin(); !IteratorEquals(it, range.end()); ++it)
211 {
212 ElementType const& value = *it;
213 bool isFirst = IteratorEquals(it, range.begin());
215 value,
216 isFirst ? FString() : rangeFormatOptions.Separator,
217 chunks,
218 position, buffer
219 );
220 }
221 FString output = FString::ConstructFromPtrSize(buffer.GetData(), position);
222 return rangeFormatOptions.Start + output + rangeFormatOptions.End;
223 }
224 else
225 {
226 TArray<TCHAR> buffer;
227 for (auto it = range.begin(); !IteratorEquals(it, range.end()); ++it)
228 {
229 ElementType const& value = *it;
230 FString valueString = AsString(value);
231
232 bool isFirst = IteratorEquals(it, range.begin());
234 valueString,
235 isFirst ? FString() : rangeFormatOptions.Separator,
236 chunks,
237 position, buffer
238 );
239 }
240 FString output = FString::ConstructFromPtrSize(buffer.GetData(), position);
241 return rangeFormatOptions.Start + output + rangeFormatOptions.End;
242 }
243 }
244
245 FORCEINLINE auto RenderAsString()
246 {
247 return ranges::make_pipeable([](auto&& range) { return RenderAsString(range); });
248 }
249
250 /**
251 * @brief Render a range as the given container.
252 *
253 * This functor will iterate over the entire input range and copy its values to the newly created container
254 * one-by-one with its `Add` function. If you want a more optimised way to do that use `OutputTo` where you can
255 * supply your own container as an l-value.
256 *
257 * usage:
258 * @code
259 * using namespace ranges;
260 * auto result = views::ints(0)
261 * | views::stride(2)
262 * | views::take(5)
263 * | RenderAs<TArray>();
264 *
265 * // -> TArray<int32> {0, 2, 4, 6, 8}
266 * @endcode
267 *
268 * @tparam Target
269 * An Unreal container template which has a public function member `Add`, the element-type of which will be deduced
270 * from the input left side range.
271 */
272 template <template <typename> typename Target>
274 {
275 template <CRangeMember From, typename Value = TRangeElementType<From>>
277 Target<Value> Convert(From&& range) const
278 {
279 Target<Value> result;
280 for (Value const& value : range)
281 result.Add(value);
282 return result;
283 }
284
285 public:
287
288 template <CRangeMember From>
289 friend auto operator | (From&& range, RenderAs&& functor)
290 {
291 return functor.Convert(range);
292 }
293
294 template <CRangeMember From>
295 auto Render(From&& range) const
296 {
297 return Convert(range);
298 }
299 };
300
301 /**
302 * @brief Render a range to an already existing container.
303 *
304 * This functor will iterate over the entire input range and copy its values to the given container one-by-one.
305 * Target container must expose iterators which allows modifying its content. If the input range has more items
306 * than the target container current size, then start using its `Add` function.
307 *
308 * usage:
309 * @code
310 * using namespace ranges;
311 * TArray<int32> Storage;
312 * Storage.SetNumUninitialized(5);
313 *
314 * auto result = views::ints(0, 10)
315 * | views::stride(2)
316 * | OutputTo(Storage);
317 *
318 * // -> TArray<int32> {0, 2, 4, 6, 8}
319 * @endcode
320 *
321 * @tparam Target
322 * An Unreal container which has a public function member `Add`, the element-type of which will be deduced from
323 * the target output container.
324 */
325 template <CUnrealRange Target>
327 {
328 using ElementType = TRangeElementType<Target>;
329 Target& Storage;
330
331 template <CRangeMember From, CConvertibleToDecayed<ElementType> Value = TRangeElementType<From>>
332 void Convert(From&& range)
333 {
334 auto it = Storage.begin();
335 auto endIt = Storage.end();
336 for (Value const& value : range)
337 {
338 if (IteratorEquals(it, endIt))
339 Storage.Add(value);
340 else
341 {
342 *it = value;
343 ++it;
344 }
345 }
346 }
347
348 public:
349 OutputTo(Target& target) : Storage(target) {}
350
351 template <CRangeMember From>
352 friend Target& operator | (From&& range, OutputTo&& functor)
353 {
354 functor.Convert(range);
355 return functor.Storage;
356 }
357 };
358
359 /**
360 * @brief
361 * Render a range of tuples or range of ranges with at least 2 elements as a TMap.
362 *
363 * This functor will iterate over the entire input range and copy its values to the newly created container
364 * one-by-one with its `Add` function.
365 *
366 * When working with range-of-ranges then ranges which doesn't have at least two elements will be silently ignored.
367 *
368 * usage (from tuples):
369 * @code
370 * using namespace ranges;
371 * TArray<int32> MyKeyArray {1, 2, 3, 4, 5};
372 * TArray<FString> MyValueArray {TEXT_"foo", TEXT_"bar"};
373 *
374 * auto result = views::zip(MyKeyArray, views::cycle(MyValueArray))
375 * | RenderAsMap();
376 *
377 * // -> TMap<int32, FString> {{1, "foo"}, {2, "bar"}, {3, "foo"}, {4, "bar"}, {5, "foo"}}
378 * @endcode
379 *
380 * usage (from inner-ranges):
381 * @code
382 * using namespace ranges;
383 * auto result = views::ints(0, 9)
384 * | views::chunk(2)
385 * | RenderAsMap();
386 *
387 * // -> TMap<int32, int32> {{0, 1}, {2, 3}, {4, 5}, {6, 7}}
388 * // notice how 8 is discarded from the end, as that range didn't have 2 items
389 * @endcode
390 */
392 {
393 template <
394 CRangeMember From,
395 CTuple Value = TRangeElementType<From>,
396 typename MapType = TMap<TTypeAtDecayed<0, Value>, TTypeAtDecayed<1, Value>>
397 >
398 requires (GetSize<Value>() >= 2)
399 static void Convert(From&& range, MapType& result)
400 {
401 for (Value const& value : range)
402 result.Add(GetItem<0>(value), GetItem<1>(value));
403 }
404
405 template <
406 CRangeMember From,
407 CRangeMember InnerRange = TRangeElementType<From>,
408 typename Value = TRangeElementType<InnerRange>,
409 typename MapType = TMap<Value, Value>
410 >
411 static void Convert(From&& range, MapType& result)
412 {
413 for (InnerRange const& innerRange : range)
414 {
415 // TODO: support TMultiMap
416 auto it = innerRange.begin();
417 if (IteratorEquals(it, innerRange.end())) continue;
418 Value const& key = *it;
419 ++it;
420 if (IteratorEquals(it, innerRange.end())) continue;
421 Value const& value = *it;
422 result.Add(key, value);
423 }
424 }
425
426 template <
427 CRangeMember From,
428 CTuple Value = TRangeElementType<From>,
429 typename MapType = TMap<TTypeAtDecayed<0, Value>, TTypeAtDecayed<1, Value>>
430 >
431 requires (GetSize<Value>() >= 2)
432 MapType Convert(From&& range) const
433 {
434 MapType result;
435 Convert(range, result);
436 return result;
437 }
438
439 template <
440 CRangeMember From,
441 CRangeMember InnerRange = TRangeElementType<From>,
442 typename Value = TRangeElementType<InnerRange>,
443 typename MapType = TMap<Value, Value>
444 >
445 MapType Convert(From&& range) const
446 {
447 MapType result;
448 Convert(range, result);
449 return result;
450 }
451
452 public:
453 template <CIsTemplate<TMap> Target>
454 friend class OutputToMap;
455
457
458 template <CRangeMember From>
459 friend auto operator | (From&& range, RenderAsMap&& functor)
460 {
461 return functor.Convert(range);
462 }
463
464 template <CRangeMember From>
465 auto Render(From&& range) const
466 {
467 return Convert(range);
468 }
469 };
470
471 /**
472 * @brief
473 * Output a range of tuples or range of ranges with at least 2 elements to an already existing TMap.
474 *
475 * This functor will iterate over the entire input range and copy its values to the existing TMap one-by-one with
476 * its `Add` function.
477 *
478 * When working with range-of-ranges then ranges which doesn't have at least two elements will be silently ignored.
479 *
480 * usage (from tuples):
481 * @code
482 * using namespace ranges;
483 * TArray<int32> MyKeyArray {1, 2, 3, 4, 5};
484 * TArray<FString> MyValueArray {TEXT_"foo", TEXT_"bar"};
485 *
486 * auto result = views::zip(MyKeyArray, views::cycle(MyValueArray))
487 * | RenderAsMap();
488 *
489 * // -> TMap<int32, FString> {{1, "foo"}, {2, "bar"}, {3, "foo"}, {4, "bar"}, {5, "foo"}}
490 * @endcode
491 *
492 * usage (from inner-ranges):
493 * @code
494 * using namespace ranges;
495 * auto result = views::ints(0, 9)
496 * | views::chunk(2)
497 * | RenderAsMap();
498 *
499 * // -> TMap<int32, int32> {{0, 1}, {2, 3}, {4, 5}, {6, 7}}
500 * // notice how 8 is discarded from the end, as that range didn't have 2 items
501 * @endcode
502 *
503 * @tparam Target A TMap. Its key-value types will be deduced from the target output map.
504 */
505 template <CIsTemplate<TMap> Target>
507 {
508 using KeyType = typename Target::KeyType;
509 using ValueType = typename Target::ValueType;
510 Target& Storage;
511
512 public:
513 OutputToMap(Target& target) : Storage(target) {};
514
515 template <
516 CRangeMember From,
517 CTupleConvertsToArgs<KeyType, ValueType> = TRangeElementType<From>
518 >
519 friend Target& operator | (From&& range, OutputToMap&& functor)
520 {
521 RenderAsMap::Convert(range, functor.Storage);
522 return functor.Storage;
523 }
524 };
525}
526
527namespace Mcro::Text
528{
529 using namespace Mcro::Range;
530
531 template <CRangeMember Operand>
532 requires (
533 !CDirectStringFormatArgument<Operand>
534 && !CHasToString<Operand>
535 )
537 {
538 template <CConvertibleToDecayed<Operand> Arg>
539 FString operator () (Arg&& left) const { return RenderAsString(left); }
540 };
541}
This header exists because STL headers in Android doesn't define STL concepts (other than same_as whi...
Use this header and Start.h in tandem to include third-party library headers which may not tolerate U...
auto end(TArray< T, A > &r) -> T *
Definition Range.h:96
Use this header and End.h in tandem to include third-party library headers which may not tolerate Unr...
#define TEXT_
A convenience alternative to Unreal's own TEXT macro but this one doesn't require parenthesis around ...
Definition TextMacros.h:51
Output a range of tuples or range of ranges with at least 2 elements to an already existing TMap.
Definition Conversion.h:507
friend Target & operator|(From &&range, OutputToMap &&functor)
Definition Conversion.h:519
OutputToMap(Target &target)
Definition Conversion.h:513
Render a range to an already existing container.
Definition Conversion.h:327
OutputTo(Target &target)
Definition Conversion.h:349
friend Target & operator|(From &&range, OutputTo &&functor)
Definition Conversion.h:352
Render a range of tuples or range of ranges with at least 2 elements as a TMap.
Definition Conversion.h:392
auto Render(From &&range) const
Definition Conversion.h:465
friend auto operator|(From &&range, RenderAsMap &&functor)
Definition Conversion.h:459
Render a range as the given container.
Definition Conversion.h:274
auto Render(From &&range) const
Definition Conversion.h:295
friend auto operator|(From &&range, RenderAs &&functor)
Definition Conversion.h:289
void CopyCharactersToBuffer(CharType const &value, int32 chunks, int32 &position, TArray< CharType > &buffer)
Definition Conversion.h:139
void CopyStringItemToBuffer(String const &value, FString const &separator, int32 chunks, int32 &position, TArray< TCHAR > &buffer)
Definition Conversion.h:154
void CopyStringToBufferUnsafe(String const &value, int32 &position, TArray< TCHAR > &buffer)
Definition Conversion.h:147
FORCEINLINE auto NoSeparator()
Don't use a separator when this range is rendered to a string.
Definition Conversion.h:90
FORCEINLINE auto NoEnclosure()
Don't enclose this range in anything when it's rendered to a string.
Definition Conversion.h:104
FORCEINLINE auto Separator(FString const &separator)
Specify a separator sequence for a range when converting it to a string.
Definition Conversion.h:61
FORCEINLINE auto RenderAsString()
Definition Conversion.h:245
bool IteratorEquals(L const &l, R const &r)
Definition Concepts.h:47
FORCEINLINE auto Enclosure(FString const &start, FString const &end)
Specify a start and an end sequence enclosing this range when converting it to a string.
Definition Conversion.h:75
FORCEINLINE auto NoDecorators()
Don't insert anything else than the contents of the input range when that is rendered as a string jus...
Definition Conversion.h:123
TIteratorElementType< decltype(DeclVal< T >().begin())> TRangeElementType
return a range's associated content type determined by dereferencing their iterator.
Definition Concepts.h:163
This namespace provide some introspection into template instantiations.
Definition Templates.h:27
Mixed text utilities and type traits.
Definition Enums.h:51
FString UnrealConvert(T const &stdStr)
Create a copy and convert an input STL string to TCHAR.
Definition Text.h:188
FString AsString(T &&input)
Attempt to convert anything to string which can tell via some method how to do so.
Definition Text.h:388
std::basic_string_view< CharT, Traits > TStdStringView
Unreal style alias for STL string views.
Definition Text.h:51
decltype(auto) GetItem(T &&tuple)
Definition Tuples.h:104
std::decay_t< typename TTypeAt_Struct< I, T >::Type > TTypeAtDecayed
Definition Tuples.h:155
consteval size_t GetSize()
Definition Tuples.h:122
TRangeWithStringFormat(Range const &range, FRangeStringFormatOptions const &options)
Definition Conversion.h:46