MCRO
C++23 utilities for Unreal Engine.
Loading...
Searching...
No Matches
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 Mcro::Text::Detail::MakeStringFromPtrSize(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 = Mcro::Text::Detail::MakeStringFromPtrSize(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 = Mcro::Text::Detail::MakeStringFromPtrSize(buffer.GetData(), position);
241 return rangeFormatOptions.Start + output + rangeFormatOptions.End;
242 }
243 }
244
245 FORCEINLINE auto RenderAsString()
246 {
247 return ranges::make_pipeable([]<CRangeMember Input>(Input&& range)
248 {
249 return RenderAsString(FWD(range));
250 });
251 }
252
253 /**
254 * @brief Render a range as the given container.
255 *
256 * This functor will iterate over the entire input range and copy its values to the newly created container
257 * one-by-one with its `Add` function. If you want a more optimised way to do that use `OutputTo` where you can
258 * supply your own container as an l-value.
259 *
260 * usage:
261 * @code
262 * using namespace ranges;
263 * auto result = views::ints(0)
264 * | views::stride(2)
265 * | views::take(5)
266 * | RenderAs<TArray>();
267 *
268 * // -> TArray<int32> {0, 2, 4, 6, 8}
269 * @endcode
270 *
271 * @tparam Target
272 * An Unreal container template which has a public function member `Add`, the element-type of which will be deduced
273 * from the input left side range.
274 */
275 template <template <typename> typename Target>
277 {
278 template <CRangeMember From, typename Value = TRangeElementType<From>>
280 Target<Value> Convert(From&& range) const
281 {
282 Target<Value> result;
283 for (Value const& value : range)
284 result.Add(value);
285 return result;
286 }
287
288 public:
290
291 template <CRangeMember From>
292 friend auto operator | (From&& range, RenderAs&& functor)
293 {
294 return functor.Convert(FWD(range));
295 }
296
297 template <CRangeMember From>
298 auto Render(From&& range) const
299 {
300 return Convert(FWD(range));
301 }
302 };
303
304 /**
305 * @brief Render a range to an already existing container.
306 *
307 * This functor will iterate over the entire input range and copy its values to the given container one-by-one.
308 * Target container must expose iterators which allows modifying its content. If the input range has more items
309 * than the target container current size, then start using its `Add` function.
310 *
311 * usage:
312 * @code
313 * using namespace ranges;
314 * TArray<int32> Storage;
315 * Storage.SetNumUninitialized(5);
316 *
317 * auto result = views::ints(0, 10)
318 * | views::stride(2)
319 * | OutputTo(Storage);
320 *
321 * // -> TArray<int32> {0, 2, 4, 6, 8}
322 * @endcode
323 *
324 * @tparam Target
325 * An Unreal container which has a public function member `Add`, the element-type of which will be deduced from
326 * the target output container.
327 */
328 template <CUnrealRange Target>
330 {
331 using ElementType = TRangeElementType<Target>;
332 Target& Storage;
333
334 template <CRangeMember From, CConvertibleToDecayed<ElementType> Value = TRangeElementType<From>>
335 void Convert(From&& range)
336 {
337 auto it = Storage.begin();
338 auto endIt = Storage.end();
339 for (Value const& value : range)
340 {
341 if (IteratorEquals(it, endIt))
342 Storage.Add(value);
343 else
344 {
345 *it = value;
346 ++it;
347 }
348 }
349 }
350
351 public:
352 OutputTo(Target& target) : Storage(target) {}
353
354 template <CRangeMember From>
355 friend Target& operator | (From&& range, OutputTo&& functor)
356 {
357 functor.Convert(FWD(range));
358 return functor.Storage;
359 }
360 };
361
362 /**
363 * @brief
364 * Render a range of tuples or range of ranges with at least 2 elements as a TMap.
365 *
366 * This functor will iterate over the entire input range and copy its values to the newly created container
367 * one-by-one with its `Add` function.
368 *
369 * When working with range-of-ranges then ranges which doesn't have at least two elements will be silently ignored.
370 *
371 * usage (from tuples):
372 * @code
373 * using namespace ranges;
374 * TArray<int32> MyKeyArray {1, 2, 3, 4, 5};
375 * TArray<FString> MyValueArray {TEXT_"foo", TEXT_"bar"};
376 *
377 * auto result = views::zip(MyKeyArray, views::cycle(MyValueArray))
378 * | RenderAsMap();
379 *
380 * // -> TMap<int32, FString> {{1, "foo"}, {2, "bar"}, {3, "foo"}, {4, "bar"}, {5, "foo"}}
381 * @endcode
382 *
383 * usage (from inner-ranges):
384 * @code
385 * using namespace ranges;
386 * auto result = views::ints(0, 9)
387 * | views::chunk(2)
388 * | RenderAsMap();
389 *
390 * // -> TMap<int32, int32> {{0, 1}, {2, 3}, {4, 5}, {6, 7}}
391 * // notice how 8 is discarded from the end, as that range didn't have 2 items
392 * @endcode
393 */
395 {
396 template <
397 CRangeMember From,
398 CTuple Value = TRangeElementType<From>,
399 typename MapType = TMap<TTypeAtDecayed<0, Value>, TTypeAtDecayed<1, Value>>
400 >
401 requires (GetSize<Value>() >= 2)
402 static void Convert(From&& range, MapType& result)
403 {
404 for (Value const& value : range)
405 result.Add(GetItem<0>(value), GetItem<1>(value));
406 }
407
408 template <
409 CRangeMember From,
410 CRangeMember InnerRange = TRangeElementType<From>,
411 typename Value = TRangeElementType<InnerRange>,
412 typename MapType = TMap<Value, Value>
413 >
414 static void Convert(From&& range, MapType& result)
415 {
416 for (InnerRange const& innerRange : range)
417 {
418 // TODO: support TMultiMap
419 auto it = innerRange.begin();
420 if (IteratorEquals(it, innerRange.end())) continue;
421 Value const& key = *it;
422 ++it;
423 if (IteratorEquals(it, innerRange.end())) continue;
424 Value const& value = *it;
425 result.Add(key, value);
426 }
427 }
428
429 template <
430 CRangeMember From,
431 CTuple Value = TRangeElementType<From>,
432 typename MapType = TMap<TTypeAtDecayed<0, Value>, TTypeAtDecayed<1, Value>>
433 >
434 requires (GetSize<Value>() >= 2)
435 MapType Convert(From&& range) const
436 {
437 MapType result;
438 Convert(FWD(range), result);
439 return result;
440 }
441
442 template <
443 CRangeMember From,
444 CRangeMember InnerRange = TRangeElementType<From>,
445 typename Value = TRangeElementType<InnerRange>,
446 typename MapType = TMap<Value, Value>
447 >
448 MapType Convert(From&& range) const
449 {
450 MapType result;
451 Convert(FWD(range), result);
452 return result;
453 }
454
455 public:
456 template <CIsTemplate<TMap> Target>
457 friend class OutputToMap;
458
460
461 template <CRangeMember From>
462 friend auto operator | (From&& range, RenderAsMap&& functor)
463 {
464 return functor.Convert(FWD(range));
465 }
466
467 template <CRangeMember From>
468 auto Render(From&& range) const
469 {
470 return Convert(FWD(range));
471 }
472 };
473
474 /**
475 * @brief
476 * Output a range of tuples or range of ranges with at least 2 elements to an already existing TMap.
477 *
478 * This functor will iterate over the entire input range and copy its values to the existing TMap one-by-one with
479 * its `Add` function.
480 *
481 * When working with range-of-ranges then ranges which doesn't have at least two elements will be silently ignored.
482 *
483 * usage (from tuples):
484 * @code
485 * using namespace ranges;
486 * TArray<int32> MyKeyArray {1, 2, 3, 4, 5};
487 * TArray<FString> MyValueArray {TEXT_"foo", TEXT_"bar"};
488 *
489 * auto result = views::zip(MyKeyArray, views::cycle(MyValueArray))
490 * | RenderAsMap();
491 *
492 * // -> TMap<int32, FString> {{1, "foo"}, {2, "bar"}, {3, "foo"}, {4, "bar"}, {5, "foo"}}
493 * @endcode
494 *
495 * usage (from inner-ranges):
496 * @code
497 * using namespace ranges;
498 * auto result = views::ints(0, 9)
499 * | views::chunk(2)
500 * | RenderAsMap();
501 *
502 * // -> TMap<int32, int32> {{0, 1}, {2, 3}, {4, 5}, {6, 7}}
503 * // notice how 8 is discarded from the end, as that range didn't have 2 items
504 * @endcode
505 *
506 * @tparam Target A TMap. Its key-value types will be deduced from the target output map.
507 */
508 template <CIsTemplate<TMap> Target>
510 {
511 using KeyType = typename Target::KeyType;
512 using ValueType = typename Target::ValueType;
513 Target& Storage;
514
515 public:
516 OutputToMap(Target& target) : Storage(target) {};
517
518 template <
519 CRangeMember From,
520 CTupleConvertsToArgs<KeyType, ValueType> = TRangeElementType<From>
521 >
522 friend Target& operator | (From&& range, OutputToMap&& functor)
523 {
524 RenderAsMap::Convert(FWD(range), functor.Storage);
525 return functor.Storage;
526 }
527 };
528}
529
530namespace Mcro::Text
531{
532 using namespace Mcro::Range;
533
534 template <CRangeMember Operand>
535 requires (
536 !CDirectStringFormatArgument<Operand>
537 && !CHasToString<Operand>
538 )
540 {
541 template <CConvertibleToDecayed<Operand> Arg>
542 FString operator () (Arg&& left) const { return RenderAsString(left); }
543 };
544}
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...
#define FWD(...)
Shorten forwarding expression with this macro so one may not need to specify explicit type.
Definition Macros.h:100
auto end(TArray< T, A > &r) -> T *
Definition Range.h:99
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:53
Output a range of tuples or range of ranges with at least 2 elements to an already existing TMap.
Definition Conversion.h:510
friend Target & operator|(From &&range, OutputToMap &&functor)
Definition Conversion.h:522
OutputToMap(Target &target)
Definition Conversion.h:516
Render a range to an already existing container.
Definition Conversion.h:330
OutputTo(Target &target)
Definition Conversion.h:352
friend Target & operator|(From &&range, OutputTo &&functor)
Definition Conversion.h:355
Render a range of tuples or range of ranges with at least 2 elements as a TMap.
Definition Conversion.h:395
auto Render(From &&range) const
Definition Conversion.h:468
friend auto operator|(From &&range, RenderAsMap &&functor)
Definition Conversion.h:462
Render a range as the given container.
Definition Conversion.h:277
auto Render(From &&range) const
Definition Conversion.h:298
friend auto operator|(From &&range, RenderAs &&functor)
Definition Conversion.h:292
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:45
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:161
This namespace provides templating utilities and introspection into template instantiations.
Definition Templates.h:21
FORCEINLINE FString MakeStringFromPtrSize(const TCHAR *ptr, int32 size)
Definition Text.h:135
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:199
FString AsString(T &&input)
Attempt to convert anything to string which can tell via some method how to do so.
Definition Text.h:397
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:106
std::decay_t< typename TTypeAt_Struct< I, T >::Type > TTypeAtDecayed
Definition Tuples.h:157
consteval size_t GetSize()
Definition Tuples.h:124
TRangeWithStringFormat(Range const &range, FRangeStringFormatOptions const &options)
Definition Conversion.h:46