topic "U++ Core Tutorial"; [l288;i1120;a17;O9;~~~.1408;2 $$1,0#10431211400427159095818037425705:param] [a83;*R6 $$2,5#31310162474203024125188417583966:caption] [b83;*4 $$3,5#07864147445237544204411237157677:title] [i288;O9;C2 $$4,6#40027414424643823182269349404212:item] [b42;a42;ph2 $$5,5#45413000475342174754091244180557:text] [l288;b17;a17;2 $$6,6#27521748481378242620020725143825:desc] [l321;C@5;1 $$7,7#20902679421464641399138805415013:code] [b2503;2 $$8,0#65142375456100023862071332075487:separator] [*@(0.0.255)2 $$9,0#83433469410354161042741608181528:base] [C2 $$10,0#37138531426314131251341829483380:class] [l288;a17;*1 $$11,11#70004532496200323422659154056402:requirement] [i417;b42;a42;O9;~~~.416;2 $$12,12#10566046415157235020018451313112:tparam] [b167;C2 $$13,13#92430459443460461911108080531343:item1] [i288;a42;O9;C2 $$14,14#77422149456609303542238260500223:item2] [*@2$(0.128.128)2 $$15,15#34511555403152284025741354420178:NewsDate] [l321;*C$7;2 $$16,16#03451589433145915344929335295360:result] [l321;*C$7;2 $$17,17#07531550463529505371228428965313:result`-line] [l160;*C+117 $$18,5#88603949442205825958800053222425:package`-title] [2 $$19,0#53580023442335529039900623488521:gap] [C2 $$20,20#70211524482531209251820423858195:class`-nested] [b50;2 $$21,21#03324558446220344731010354752573:Par] [H8;b73;*+150 $$22,5#07864147445237544204111237153677:subtitle] [2 $$0,0#00000000000000000000000000000000:Default] [{_} [s2;%% U`+`+ Core Tutorial&] [s22; Table of contents&] [s0;^`#Chapter`_1^ &] [s0; [^`#Chapter`_1^ 1. Basics]&] [s0; ___[^`#Section`_1`_1^ 1.1 Logging]&] [s0; ___[^`#Section`_1`_2^ 1.2 String]&] [s0; ___[^`#Section`_1`_3^ 1.3 StringBuffer]&] [s0; ___[^`#Section`_1`_4^ 1.4 WString]&] [s0; ___[^`#Section`_1`_5^ 1.5 Date and Time]&] [s0; ___[^`#Section`_1`_6^ 1.6 AsString, ToString and operator<<]&] [s0; ___[^`#Section`_1`_7^ 1.7 CombineHash]&] [s0; ___[^`#Section`_1`_8^ 1.8 SgnCompare and CombineCompare]&] [s0; &] [s0; [^`#Chapter`_2^ 2. Streams]&] [s0; ___[^`#Section`_2`_1^ 2.1 Streams basics]&] [s0; ___[^`#Section`_2`_2^ 2.2 Special streams]&] [s0; ___[^`#Section`_2`_3^ 2.3 Binary serialization]&] [s0; &] [s0; [^`#Chapter`_3^ 3. Array containers]&] [s0; ___[^`#Section`_3`_1^ 3.1 Vector basics]&] [s0; ___[^`#Section`_3`_2^ 3.2 Vector operations]&] [s0; ___[^`#Section`_3`_3^ 3.3 Transfer issues]&] [s0; ___[^`#Section`_3`_4^ 3.4 Client types in U`+`+ containers]&] [s0; ___[^`#Section`_3`_5^ 3.5 Array flavor]&] [s0; ___[^`#Section`_3`_6^ 3.6 Polymorphic Array]&] [s0; ___[^`#Section`_3`_7^ 3.7 Bidirectional containers]&] [s0; ___[^`#Section`_3`_8^ 3.8 Index]&] [s0; ___[^`#Section`_3`_9^ 3.9 Index and client types]&] [s0; ___[^`#Section`_3`_10^ 3.10 VectorMap, ArrayMap]&] [s0; ___[^`#Section`_3`_11^ 3.11 One]&] [s0; ___[^`#Section`_3`_12^ 3.12 Any]&] [s0; ___[^`#Section`_3`_13^ 3.13 InVector, InArray]&] [s0; ___[^`#Section`_3`_14^ 3.14 SortedIndex, SortedVectorMap, SortedArrayMap]&] [s0; ___[^`#Section`_3`_15^ 3.15 Tuples]&] [s0; &] [s0; [^`#Chapter`_4^ 4. Ranges and algorithms]&] [s0; ___[^`#Section`_4`_1^ 4.1 Range]&] [s0; ___[^`#Section`_4`_2^ 4.2 Algorithms]&] [s0; ___[^`#Section`_4`_3^ 4.3 Sorting]&] [s0; &] [s0; [^`#Chapter`_5^ 5. Value]&] [s0; ___[^`#Section`_5`_1^ 5.1 Value]&] [s0; ___[^`#Section`_5`_2^ 5.2 Null]&] [s0; ___[^`#Section`_5`_3^ 5.3 Client types and Value, RawValue, RichValue]&] [s0; ___[^`#Section`_5`_4^ 5.4 ValueArray and ValueMap]&] [s0; &] [s0; [^`#Chapter`_6^ 6. Function and lambdas]&] [s0; ___[^`#Section`_6`_1^ 6.1 Function]&] [s0; ___[^`#Section`_6`_2^ 6.2 Capturing U`+`+ containers into lambdas]&] [s0; &] [s0; [^`#Chapter`_7^ 7. Multithreading]&] [s0; ___[^`#Section`_7`_1^ 7.1 Thread]&] [s0; ___[^`#Section`_7`_2^ 7.2 Mutex]&] [s0; ___[^`#Section`_7`_3^ 7.3 ConditionVariable]&] [s0; ___[^`#Section`_7`_4^ 7.4 CoWork]&] [s0; ___[^`#Section`_7`_5^ 7.5 AsyncWork]&] [s0; ___[^`#Section`_7`_6^ 7.6 CoPartition]&] [s0; ___[^`#Section`_7`_7^ 7.7 CoDo]&] [s0; ___[^`#Section`_7`_8^ 7.8 Parallel algorithms]&] [s22;:Chapter`_1: 1. Basics&] [s3;:Section`_1`_1: 1.1 Logging&] [s5; Logging is a useful technique to trace the flow of the code and examine results. In this tutorial we will be using logging extensively, so let us start tutorial with the explanation of logging.&] [s5; In debug mode and with default settings, macro [*C@5 LOG] puts string into output log file. Log file is placed into `'config`-directory`', which by default is .exe directory in Win32 and `~/.upp/appname in POSIX.&] [s5; In TheIDE, you can access the log using `'Debug`'/`'View the log file Alt`+L`'.&] [s0; &] [s7; LOG(`"Hello world`");&] [s0; &] [s17; Hello world&] [s0; &] [s5; You can log values of various types, as long as they have [*C@5 AsString] function defined You can chain values in single [*C@5 LOG] using [*C@5 operator<<]:&] [s0; &] [s7; int x `= 123;&] [s7; LOG(`"Value of x is `" << x);&] [s0; &] [s17; Value of x is 123&] [s0; &] [s5; As it is very common to log a value of single variable, [*C@5 DUMP] macro provides a useful shortcut, creating a log line with the variable name and value:&] [s0; &] [s7; DUMP(x);&] [s0; &] [s17; x `= 123&] [s0; &] [s5; To get the value in hexadecimal code, you can use [*C@5 LOGHEX] / [*C@5 DUMPHEX]&] [s0; &] [s7; DUMPHEX(x);&] [s7; String h `= `"foo`";&] [s7; DUMPHEX(h);&] [s0; &] [s17; x `= 0x7b&] [s17; h `= Memory at 0x0208fe08, size 0x3 `= 3&] [s17; `+0 0x0208FE08 66 6F 6F foo &] [s0; &] [s5; To log the value of a container (or generic Range), you can either use normal [*C@5 LOG] / [*C@5 DUMP]:&] [s0; &] [s7; Vector v `= `{ 1, 2, 3 `};&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[1, 2, 3`]&] [s0; &] [s5; or you can use DUMPC for multi`-line output:&] [s0; &] [s7; DUMPC(v);&] [s0; &] [s17; v:&] [s17; -|`[0`] `= 1&] [s17; -|`[1`] `= 2&] [s17; -|`[2`] `= 3&] [s0; &] [s5; For maps, use DUMPM:&] [s0; &] [s7; VectorMap map `= `{ `{ 1, `"one`" `}, `{ 2, `"two`" `} `};&] [s7; &] [s7; DUMP(map);&] [s0; &] [s17; map `= `{1: one, 2: two`}&] [s0; &] [s0; &] [s7; DUMPM(map);&] [s0; &] [s17; map:&] [s17; -|`[0`] `= (1) one&] [s17; -|`[1`] `= (2) two&] [s0; &] [s5; All normal [*C@5 LOG]s are removed in release mode. If you need to log things in release mode, you need to use [*C@5 LOG]/``DUMP`` variant with `'[*C@5 R]`' prefix ([*C@5 RLOG], [*C@5 RDUMP], [*C@5 RDUMPHEX]...):&] [s0; &] [s7; RLOG(`"This will be logged in release mode too!`");&] [s0; &] [s17; This will be logged in release mode too!&] [s0; &] [s5; Sort of opposite situation is when adding temporary [*C@5 LOG]s to the code for debugging. In that case, `'[*C@5 D]`' prefixed variants ([*C@5 DLOG], [*C@5 DDUMP], [*C@5 DDUMPHEX]...) are handy `- these cause compile error in release mode (unless you define the flag DEBUGCODE in the main configuration), so will not get forgotten in the code past the release:&] [s0; &] [s7; DLOG(`"This would not compile in release mode.`");&] [s0; &] [s17; This would not compile in release mode.&] [s0; &] [s5; The last flavor of [*C@5 LOG] you can encounter while reading U`+`+ sources is the one prefixed with `'[*C@5 L]`'. This one is not actually defined in U`+`+ library and is just a convention. On the start of file, there is usually something like:&] [s0; &] [s7; #define LLOG(x) // DLOG(x)&] [s0; &] [s5; and by uncommenting the body part, you can activate the logging in that particular file.&] [s5; While logging to .log file is default, there are various ways how to affect logging, for example following line adjusts logging to output the log both to the console and .log file:&] [s0; &] [s7; StdLogSetup(LOG`_COUT`|LOG`_FILE);&] [s0; &] [s3;H4;:Section`_1`_2: 1.2 String&] [s5; String is a value type useful for storing text or binary data.&] [s0; &] [s7; String a `= `"Hello`";&] [s7; DUMP(a);&] [s0; &] [s17; a `= Hello&] [s0; &] [s5; You can concatenate it with another String or literal:&] [s0; &] [s7; a `= a `+ `" world`";&] [s7; DUMP(a);&] [s0; &] [s17; a `= Hello world&] [s0; &] [s5; Or single character or specified number of characters from another [*C@5 String] or literal:&] [s0; &] [s7; a.Cat(`'!`');&] [s7; DUMP(a);&] [s0; &] [s17; a `= Hello world!&] [s0; &] [s0; &] [s7; a.Cat(`"ABCDEFGHIJKLM`", 3);&] [s7; DUMP(a);&] [s0; &] [s17; a `= Hello world!ABC&] [s0; &] [s5; [*C@5 Clear] method empties the String:&] [s0; &] [s7; a.Clear();&] [s7; DUMP(a);&] [s0; &] [s17; a `= &] [s0; &] [s5; You can use [*C@5 operator<<] to append to existing [*C@5 String]. Non`-string values are converted to appropriate [*C@5 String] representation (using standard function [*C@5 AsString], whose default template definition calls [*C@5 ToString] method for value):&] [s0; &] [s7; for(int i `= 0; i < 10; i`+`+)&] [s7; -|a << i << `", `";&] [s7; &] [s7; DUMP(a);&] [s0; &] [s17; a `= 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, &] [s0; &] [s5; Sometimes is is useful to use [*C@5 operator<<] to produce a temporary [*C@5 String] value (e.g. as real argument to function call):&] [s0; &] [s7; String b `= String() << `"Number is `" << 123 << `".`";&] [s7; &] [s7; DUMP(b);&] [s0; &] [s17; b `= Number is 123.&] [s0; &] [s5; String provides many various methods for obtaining character count, inserting characters into [*C@5 String] or removing them:&] [s0; &] [s7; a `= `"0123456789`";&] [s7; &] [s7; DUMP(a.GetCount());&] [s0; &] [s17; a.GetCount() `= 10&] [s0; &] [s0; &] [s7; DUMP(a.GetLength()); // GetLength is a synonym of GetCount&] [s0; &] [s17; a.GetLength() `= 10&] [s0; &] [s0; &] [s7; a.Insert(6, `"`");&] [s7; DUMP(a);&] [s0; &] [s17; a `= 0123456789&] [s0; &] [s0; &] [s7; a.Remove(2, 2);&] [s7; DUMP(a);&] [s0; &] [s17; a `= 01456789&] [s0; &] [s5; as well as searching and comparing methods:&] [s0; &] [s7; DUMP(a.Find(`'e`'));&] [s7; DUMP(a.ReverseFind(`'e`'));&] [s0; &] [s17; a.Find(`'e`') `= 8&] [s17; a.ReverseFind(`'e`') `= 11&] [s0; &] [s0; &] [s7; DUMP(a.Find(`"ins`"));&] [s0; &] [s17; a.Find(`"ins`") `= 5&] [s0; &] [s0; &] [s7; DUMP(a.StartsWith(`"ABC`"));&] [s7; DUMP(a.StartsWith(`"01`"));&] [s7; DUMP(a.EndsWith(`"89`"));&] [s0; &] [s17; a.StartsWith(`"ABC`") `= false&] [s17; a.StartsWith(`"01`") `= true&] [s17; a.EndsWith(`"89`") `= true&] [s0; &] [s5; You can get slice of String using Mid method; with single parameter it provides slice to the end of String:&] [s0; &] [s7; DUMP(a.Mid(3, 3));&] [s7; DUMP(a.Mid(3));&] [s0; &] [s17; a.Mid(3, 3) `= 56789&] [s0; &] [s5; You can also trim the length of String using Trim (this is faster than using any other method):&] [s0; &] [s7; a.Trim(4);&] [s7; DUMP(a);&] [s0; &] [s17; a `= 0145&] [s0; &] [s5; You can obtain integer values of individual characters using operator`[`]:&] [s0; &] [s7; DUMP(a`[0`]);&] [s0; &] [s17; a`[0`] `= 48&] [s0; &] [s5; or the value of first character using operator`* (note that if [*C@5 GetCount() `=`= 0], this will return zero terminator):&] [s0; &] [s7; DUMP(`*a);&] [s0; &] [s17; `*a `= 48&] [s0; &] [s0; &] [s7; a.Clear();&] [s7; &] [s7; DUMP(`*a);&] [s0; &] [s17; `*a `= 0&] [s0; &] [s5; [*C@5 String] has implicit cast to zero terminated [*C@5 const char `*ptr] (only valid as long as [*C@5 String] does not mutate:&] [s0; &] [s7; a `= `"1234`";&] [s7; const char `*s `= a;&] [s7; while(`*s)&] [s7; -|LOG(`*s`+`+);&] [s0; &] [s17; 1&] [s17; 2&] [s17; 3&] [s17; 4&] [s0; &] [s5; [*C@5 String] also has standard [*C@5 begin] [*C@5 end] methods, which e.g. allows for C`+`+11 [*C@5 for]:&] [s0; &] [s7; for(char ch : a)&] [s7; -|LOG(ch);&] [s0; &] [s17; 1&] [s17; 2&] [s17; 3&] [s17; 4&] [s0; &] [s5; It is absolutely OK and common to use String for storing binary data, including zeroes:&] [s0; &] [s7; a.Cat(0);&] [s7; &] [s7; DUMPHEX(a);&] [s0; &] [s17; a `= Memory at 0x0208fde0, size 0x5 `= 5&] [s17; `+0 0x0208FDE0 31 32 33 34 00 1234. &] [s0; &] [s3;H4;:Section`_1`_3: 1.3 StringBuffer&] [s5; If you need a direct write access to [*C@5 String]`'s C`-string character buffer, you can use complementary [*C@5 StringBuffer] class. One of reasons to do so is when you have to deal with some C`-API functions that expects to write directly to [*C@5 char `*] and you would like that result converted to the [*C@5 String]:&] [s0; &] [s7; void CApiFunction(char `*c)&] [s7; `{&] [s7; -|strcpy(c, `"Hello`");&] [s7; `}&] [s7; &] [s7; StringBuffer b;&] [s7; b.SetLength(200);&] [s7; CApiFunction(b);&] [s7; b.Strlen();&] [s7; String x `= b;&] [s7; &] [s7; DUMP(x);&] [s0; &] [s17; x `= Hello&] [s0; &] [s5; In this case, [*C@5 SetLength] creates a C array of 200 characters. You can then call C`-API function. Later you set the real length using [*C@5 Strlen] `- this function performs strlen of buffer and sets the length accordingly. Later you simply assign the [*C@5 StringBuffer] to [*C@5 String]. Note that for performance reasons, this operation clears the [*C@5 StringBuffer] content (operation is fast and does not depend on the number of characters).&] [s5; Another usage scenario of StringBuffer is altering existing String:&] [s0; &] [s7; b `= x;&] [s7; b`[1`] `= `'a`';&] [s7; x `= b;&] [s7; &] [s7; DUMP(x);&] [s0; &] [s17; x `= Hallo&] [s0; &] [s5; Similar to assigning StringBuffer to String, assigning String to StringBuffer clears the source String.&] [s5; StringBuffer also provides appending operations:&] [s0; &] [s7; b `= x;&] [s7; b.Cat(`'!`');&] [s7; x `= b;&] [s7; &] [s7; DUMP(x);&] [s0; &] [s17; x `= Hallo!&] [s0; &] [s5; Note that sometimes when creating some String from a lot of single characters, using StringBuffer for the operation is slightly faster then using String directly.&] [s3;H4;:Section`_1`_4: 1.4 WString&] [s5; String works with 8 bit characters. For 16`-bit character encoding use [*C@5 WString]. Both classes are closely related and share most of interface methods. U`+`+ also provides conversions between [*C@5 String] and [*C@5 WString] and you can also use 8 bit string literals with [*C@5 WString]. Conversion is ruled by current default character set. Default value of default character set is [*C@5 CHARSET`_UTF8]. This conversion is also used in [*C@5 WString`::ToString], e.g. when putting [*C@5 WString] to log:&] [s0; &] [s7; WString x `= `"characters 280`-300: `"; // you can assign 8`-bit character literal to WString&] [s7; for(int i `= 280; i < 300; i`+`+)&] [s7; -|x.Cat(i);&] [s7; &] [s7; DUMP(x);&] [s0; &] [s17; x `= characters 280`-300: ĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪī&] [s0; &] [s5; [*C@5 ToString] converts [*C@5 WString] to [*C@5 String]:&] [s0; &] [s7; String y `= x.ToString();&] [s7; DUMP(y);&] [s0; &] [s17; y `= characters 280`-300: ĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪī&] [s0; &] [s5; [*C@5 ToWString] converts [*C@5 String] to [*C@5 WString]:&] [s0; &] [s7; y.Cat(`" (appended)`"); // you can use 8`-bit character literals in most WString operations&] [s7; x `= y.ToWString();&] [s7; &] [s7; DUMP(x);&] [s0; &] [s17; x `= characters 280`-300: ĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪī (appended)&] [s0; &] [s3;H4;:Section`_1`_5: 1.5 Date and Time&] [s5; To represent date and time, U`+`+ provides [*C@5 Date] and [*C@5 Time] concrete types.&] [s0; &] [s7; Date date `= GetSysDate();&] [s7; &] [s7; DUMP(date);&] [s0; &] [s17; date `= 07/21/2021&] [s0; &] [s5; All data members of [*C@5 Date] structure are public:&] [s0; &] [s7; DUMP((int)date.year); // we need to cast to int because some date members&] [s7; DUMP((int)date.month); // are of unsigned character type which would log&] [s7; DUMP((int)date.day); // as characters&] [s0; &] [s17; (int)date.year `= 2021&] [s17; (int)date.month `= 7&] [s17; (int)date.day `= 21&] [s0; &] [s5; Dates can be compared:&] [s0; &] [s7; DUMP(date > Date(2000, 1, 1));&] [s0; &] [s17; date > Date(2000, 1, 1) `= true&] [s0; &] [s5; Adding a number to [*C@5 Date] adds a number of days to it, incrementing/decrement ing goes to the next/previous day:&] [s0; &] [s7; DUMP(date `+ 1);&] [s7; DUMP(`-`-date);&] [s7; DUMP(`+`+date);&] [s0; &] [s17; date `+ 1 `= 07/22/2021&] [s17; `-`-date `= 07/20/2021&] [s17; `+`+date `= 07/21/2021&] [s0; &] [s5; Subtraction of dates yields a number of days between them:&] [s0; &] [s7; DUMP(date `- Date(2000, 1, 1));&] [s0; &] [s17; date `- Date(2000, 1, 1) `= 7872&] [s0; &] [s5; There are several [*C@5 Date] and calendar related functions:&] [s0; &] [s7; DUMP(IsLeapYear(2012));&] [s7; DUMP(IsLeapYear(2014));&] [s7; DUMP(IsLeapYear(2015));&] [s7; DUMP(IsLeapYear(2016));&] [s7; DUMP(IsLeapYear(2017));&] [s0; &] [s17; IsLeapYear(2012) `= true&] [s17; IsLeapYear(2014) `= false&] [s17; IsLeapYear(2015) `= false&] [s17; IsLeapYear(2016) `= true&] [s17; IsLeapYear(2017) `= false&] [s0; &] [s0; &] [s7; DUMP(GetDaysOfMonth(2, 2015));&] [s7; DUMP(GetDaysOfMonth(2, 2016));&] [s0; &] [s17; GetDaysOfMonth(2, 2015) `= 28&] [s17; GetDaysOfMonth(2, 2016) `= 29&] [s0; &] [s0; &] [s7; DUMP(DayOfWeek(date)); // 0 is Sunday&] [s0; &] [s17; DayOfWeek(date) `= 3&] [s0; &] [s0; &] [s7; DUMP(LastDayOfMonth(date));&] [s7; DUMP(FirstDayOfMonth(date));&] [s7; DUMP(LastDayOfYear(date));&] [s7; DUMP(FirstDayOfYear(date));&] [s7; DUMP(DayOfYear(date)); // number of days since Jan`-1 `+ 1&] [s7; DUMP(DayOfYear(Date(2016, 1, 1)));&] [s0; &] [s17; LastDayOfMonth(date) `= 07/31/2021&] [s17; FirstDayOfMonth(date) `= 07/01/2021&] [s17; LastDayOfYear(date) `= 12/31/2021&] [s17; FirstDayOfYear(date) `= 01/01/2021&] [s17; DayOfYear(date) `= 202&] [s17; DayOfYear(Date(2016, 1, 1)) `= 1&] [s0; &] [s0; &] [s7; DUMP(AddMonths(date, 20));&] [s7; DUMP(GetMonths(date, date `+ 100)); // number of `'whole months`' between two dates&] [s7; DUMP(GetMonthsP(date, date `+ 100)); // number of `'whole or partial months`' between two dates&] [s7; DUMP(AddYears(date, 2));&] [s0; &] [s17; AddMonths(date, 20) `= 03/21/2023&] [s17; GetMonths(date, date `+ 100) `= 3&] [s17; GetMonthsP(date, date `+ 100) `= 4&] [s17; AddYears(date, 2) `= 07/21/2023&] [s0; &] [s0; &] [s7; DUMP(GetWeekDate(2015, 1));&] [s7; int year;&] [s7; DUMP(GetWeek(Date(2016, 1, 1), year)); // first day of year can belong to previous year&] [s7; DUMP(year);&] [s0; &] [s17; GetWeekDate(2015, 1) `= 12/29/2014&] [s17; GetWeek(Date(2016, 1, 1), year) `= 53&] [s17; year `= 2015&] [s0; &] [s0; &] [s7; DUMP(EasterDay(2015));&] [s7; DUMP(EasterDay(2016));&] [s0; &] [s17; EasterDay(2015) `= 04/05/2015&] [s17; EasterDay(2016) `= 03/27/2016&] [s0; &] [s5; U`+`+ defines the beginning and the end of era, most algorithms can safely assume that as minimal and maximal values [*C@5 Date] can represent:&] [s0; &] [s7; DUMP(Date`::Low());&] [s7; DUMP(Date`::High());&] [s0; &] [s17; Date`::Low() `= 01/01/`-4000&] [s17; Date`::High() `= 01/01/4000&] [s0; &] [s5; Time is derived from [*C@5 Date], adding members to represent time:&] [s0; &] [s7; Time time `= GetSysTime();&] [s7; DUMP(time);&] [s7; DUMP((Date)time);&] [s7; DUMP((int)time.hour);&] [s7; DUMP((int)time.minute);&] [s7; DUMP((int)time.second);&] [s0; &] [s17; time `= 07/21/2021 15:01:38&] [s17; (Date)time `= 07/21/2021&] [s17; (int)time.hour `= 15&] [s17; (int)time.minute `= 1&] [s17; (int)time.second `= 38&] [s0; &] [s5; Times can be compared:&] [s0; &] [s7; DUMP(time > Time(1970, 0, 0));&] [s0; &] [s17; time > Time(1970, 0, 0) `= true&] [s0; &] [s5; Warning: As [*C@5 Time] is derived from the [*C@5 Date], most operations automatically convert [*C@5 Time] back to [*C@5 Date]. You have to use [*C@5 ToTime] conversion function to convert [*C@5 Date] to [*C@5 Time]:&] [s0; &] [s7; DUMP(time > date); // time gets converted to Date...&] [s7; DUMP(time > ToTime(date));&] [s0; &] [s17; time > date `= false&] [s17; time > ToTime(date) `= true&] [s0; &] [s5; Like [*C@5 Date], [*C@5 Time] supports add and subtract operations, but numbers represent seconds (using [*C@5 int64] datatype):&] [s0; &] [s7; DUMP(time `+ 1);&] [s7; DUMP(time `+ 24 `* 3600);&] [s7; DUMP(time `- date); // time converts to Date, so the result is in days&] [s7; DUMP(time `- ToTime(date)); // Time `- Time is in seconds&] [s0; &] [s17; time `+ 1 `= 07/21/2021 15:01:39&] [s17; time `+ 24 `* 3600 `= 07/22/2021 15:01:38&] [s17; time `- date `= 0&] [s17; time `- ToTime(date) `= 54098&] [s0; &] [s5; [*C@5 Time] defines era limits too:&] [s0; &] [s7; DUMP(Time`::Low());&] [s7; DUMP(Time`::High());&] [s0; &] [s17; Time`::Low() `= 01/01/`-4000 00:00:00&] [s17; Time`::High() `= 01/01/4000 00:00:00&] [s0; &] [s3;H4;:Section`_1`_6: 1.6 [C@5 AsString], [C@5 ToString] and [C@5 operator<<]&] [s5; U`+`+ Core provides simple yet effective standard schema for converting values to default textual form. System is based on the combination of template functions (following code is part of U`+`+ library):&] [s0; &] [s7; namespace Upp `{&] [s7; -|template &] [s7; -|inline String AsString(const T`& x)&] [s7; -|`{&] [s7; -|-|return x.ToString();&] [s7; -|`}&] [s7; -|&] [s7; -|template &] [s7; -|inline Stream`& operator<<(Stream`& s, const T`& x)&] [s7; -|`{&] [s7; -|-|s << AsString(x);&] [s7; -|-|return s;&] [s7; -|`}&] [s7; -|&] [s7; -|template &] [s7; -|inline String`& operator<<(String`& s, const T`& x)&] [s7; -|`{&] [s7; -|-|s.Cat(AsString(x));&] [s7; -|-|return s;&] [s7; -|`}&] [s7; `};&] [s0; &] [s5; Client types have to either define [*C@5 String ToString] method or specialize [*C@5 AsString] template in [*C@5 Upp] namespace. Such types can be appended to Streams or Strings using [*C@5 operator<<]. Of course, U`+`+ value types and primitive types have required items predefined by U`+`+:&] [s0; &] [s7; FileOut fout(ConfigFile(`"test.txt`"));&] [s7; String sout;&] [s7; &] [s7; fout << 1.23 << `' `' << GetSysDate() << `' `' << GetSysTime();&] [s7; sout << 1.23 << `' `' << GetSysDate() << `' `' << GetSysTime();&] [s7; &] [s7; fout.Close();&] [s7; &] [s7; DUMP(LoadFile(ConfigFile(`"test.txt`")));&] [s7; DUMP(sout);&] [s0; &] [s17; LoadFile(ConfigFile(`"test.txt`")) `= 1.23 07/21/2021 07/21/2021 15:01:38&] [s17; sout `= 1.23 07/21/2021 07/21/2021 15:01:38&] [s0; &] [s5; Getting client types involved into this schema is not too difficult, all you need to do is to add [*C@5 ToString] method:&] [s0; &] [s7; struct BinFoo `{&] [s7; -|int x;&] [s7; -|&] [s7; -|String ToString() const `{ return FormatIntBase(x, 2); `}&] [s7; `};&] [s7; &] [s7; BinFoo bf;&] [s7; bf.x `= 30;&] [s7; &] [s7; sout.Clear();&] [s7; sout << bf;&] [s7; DUMP(sout);&] [s0; &] [s17; sout `= 11110&] [s0; &] [s5; If you cannot add [*C@5 ToString], you can still specialize template in Upp namespace:&] [s0; &] [s7; struct RomanFoo `{&] [s7; -|int x;&] [s7; -|&] [s7; -|RomanFoo(int x) : x(x) `{`}&] [s7; `};&] [s7; &] [s7; namespace Upp `{&] [s7; template <> String Upp`::AsString(const RomanFoo`& a) `{ return FormatIntRoman(a.x); `}&] [s7; `};&] [s0; &] [s3;H4;:Section`_1`_7: 1.7 CombineHash&] [s5; To simplify providing of high quality hash codes for composite types, U`+`+ provides [*C@5 CombineHash] utility class. This class uses [*C@5 GetHashValue] function to gather hash codes of all values and combines them to provide final hash value for composite type:&] [s0; &] [s7; struct Foo `{&] [s7; -|String a;&] [s7; -|int b;&] [s7; -|&] [s7; -|unsigned GetHashValue() const `{ return CombineHash(a, b); `}&] [s7; `};&] [s0; &] [s5; Note that [*C@5 GetHashValue] is defined as function template that calls [*C@5 GetHashValue] method of its argument, therefore defining [*C@5 GetHashValue] method defines [*C@5 GetHashValue] function too:&] [s0; &] [s7; Foo x;&] [s7; x.a `= `"world`";&] [s7; x.b `= 22;&] [s7; &] [s7; DUMP(GetHashValue(x));&] [s0; &] [s17; GetHashValue(x) `= 3180644175&] [s0; &] [s0; &] [s7; x.a << `'!`';&] [s7; &] [s7; DUMP(GetHashValue(x));&] [s0; &] [s17; GetHashValue(x) `= 1959050319&] [s0; &] [s3;H4;:Section`_1`_8: 1.8 SgnCompare and CombineCompare&] [s5; Traditional approach of C language of representing comparison results was 3`-state: comparing a and b results in negative value (if a < b), zero (if a `=`= b) or positive value (a > b). In C`+`+ standard library, comparisons are usually represented with [*C@5 bool] predicates.&] [s5; However, with [*C@5 bool] predicate it becomes somewhat more difficult to provide comparisons for composite types:&] [s0; &] [s7; struct Foo `{&] [s7; -|String a;&] [s7; -|int b;&] [s7; -|int c;&] [s7; -|&] [s7; -|// we want to order Foo instances by a first, then b, then c&] [s7; -|&] [s7; -|bool operator<(const Foo`& x) const `{&] [s7; -|-|return a < x.a ? true&] [s7; -|-| : a `=`= x.a ? b < x.b ? true&] [s7; -|-| : b `=`= x.b ? false&] [s7; -|-| : c < x.c&] [s7; -|-| : false;&] [s7; -|`}&] [s7; `};&] [s0; &] [s5; U`+`+ provides standard function [*C@5 SgnCompare], which returns negative value/zero/positive in `"C style`":&] [s0; &] [s7; int a `= 1;&] [s7; int b `= 2;&] [s7; &] [s7; DUMP(SgnCompare(a, b));&] [s7; DUMP(SgnCompare(b, a));&] [s7; DUMP(SgnCompare(a, a));&] [s0; &] [s17; SgnCompare(a, b) `= `-1&] [s17; SgnCompare(b, a) `= 1&] [s17; SgnCompare(a, a) `= 0&] [s0; &] [s5; Default implementation of [*C@5 SgnCompare] calls [*C@5 Compare] method of value:&] [s0; &] [s7; struct MyClass `{&] [s7; -|int val;&] [s7; -|&] [s7; -|int Compare(const MyClass`& x) const `{ return SgnCompare(val, x.val); `}&] [s7; `};&] [s0; &] [s5; [*C@5 SgnCompare] is now defined for [*C@5 MyClass]:&] [s0; &] [s7; MyClass u, v;&] [s7; u.val `= 1;&] [s7; v.val `= 2;&] [s7; &] [s7; DUMP(SgnCompare(u, v));&] [s7; DUMP(SgnCompare(v, u));&] [s7; DUMP(SgnCompare(v, v));&] [s0; &] [s17; SgnCompare(u, v) `= `-1&] [s17; SgnCompare(v, u) `= 1&] [s17; SgnCompare(v, v) `= 0&] [s0; &] [s5; Now getting back to [*C@5 Foo], with [*C@5 SgnCompare] [*C@5 operator<] becomes much less difficult:&] [s0; &] [s7; struct Foo2 `{&] [s7; -|String a;&] [s7; -|int b;&] [s7; -|int c;&] [s7; -|&] [s7; -|bool operator<(const Foo2`& x) const `{&] [s7; -|-|int q `= SgnCompare(a, x.a);&] [s7; -|-|if(q) return q < 0;&] [s7; -|-|q `= SgnCompare(b, x.b);&] [s7; -|-|if(q) return q < 0;&] [s7; -|-|q `= SgnCompare(c, x.c);&] [s7; -|-|return q < 0;&] [s7; -|`}&] [s7; `};&] [s0; &] [s5; Alternatively, it is possible to define just [*C@5 Compare] method and use [*C@5 Comparable] [^https`:`/`/en`.wikipedia`.org`/wiki`/Curiously`_recurring`_template`_pattern^ C RTP idiom] to define all relation operators:&] [s0; &] [s7; struct Foo3 : Comparable `{&] [s7; -|String a;&] [s7; -|int b;&] [s7; -|int c;&] [s7; -|&] [s7; -|int Compare(const Foo3`& x) const `{&] [s7; -|-|int q `= SgnCompare(a, x.a);&] [s7; -|-|if(q) return q;&] [s7; -|-|q `= SgnCompare(b, x.b);&] [s7; -|-|if(q) return q;&] [s7; -|-|return SgnCompare(c, x.c);&] [s7; -|`}&] [s7; `};&] [s7; &] [s7; Foo3 m, n;&] [s7; m.a `= `"A`";&] [s7; m.b `= 1;&] [s7; m.c `= 2;&] [s7; n.a `= `"A`";&] [s7; n.b `= 1;&] [s7; n.c `= 3;&] [s7; &] [s7; DUMP(m < n);&] [s7; DUMP(m `=`= n);&] [s7; DUMP(m !`= n);&] [s7; DUMP(SgnCompare(m, n));&] [s0; &] [s17; m < n `= true&] [s17; m `=`= n `= false&] [s17; m !`= n `= true&] [s17; SgnCompare(m, n) `= `-1&] [s0; &] [s5; While the content of [*C@5 Compare] method is trivial, it can be further simplified using [*C@5 CombineCompare] helper class:&] [s0; &] [s7; struct Foo4 : Comparable `{&] [s7; -|String a;&] [s7; -|int b;&] [s7; -|int c;&] [s7; -|&] [s7; -|int Compare(const Foo4`& x) const `{&] [s7; -|-|return CombineCompare(a, x.a)(b, x.b)(c, x.c);&] [s7; -|`}&] [s7; `};&] [s7; &] [s7; Foo4 o, p;&] [s7; o.a `= `"A`";&] [s7; o.b `= 1;&] [s7; o.c `= 2;&] [s7; p.a `= `"A`";&] [s7; p.b `= 1;&] [s7; p.c `= 3;&] [s7; &] [s7; DUMP(o < p);&] [s7; DUMP(o `=`= p);&] [s7; DUMP(o !`= p);&] [s7; DUMP(SgnCompare(o, p));&] [s0; &] [s17; o < p `= true&] [s17; o `=`= p `= false&] [s17; o !`= p `= true&] [s17; SgnCompare(o, p) `= `-1&] [s0; &] [s22;:Chapter`_2: 2. Streams&] [s3;:Section`_2`_1: 2.1 Streams basics&] [s5; U`+`+ stream working with files is [*C@5 FileStream]. It has 3 derived classes, [*C@5 FileIn], [*C@5 FileOut] and [*C@5 FileAppend], for the most common uses.&] [s0; &] [s7; FileIn in(GetDataFile(`"test.txt`"));&] [s7; if(!in) `{&] [s7; -|LOG(`"Failed to open the file`");&] [s7; -|return;&] [s7; `}&] [s0; &] [s5; The most basic operations of streams are [*C@5 Put] and [*C@5 Get]. [*C@5 Get] works in the same ways as good old C getc `- it returns negative number on eof or error:&] [s0; &] [s7; String h;&] [s7; int c;&] [s7; while((c `= in.Get()) >`= 0)&] [s7; -|h.Cat(c);&] [s7; DUMP(h);&] [s0; &] [s17; h `= Lorem ipsum dolor sit amet, consectetur adipiscing elit,&] [s17; sed do eiusmod tempor incididunt ut labore et dolore magna&] [s17; aliqua. Ut enim ad minim veniam, quis nostrud exercitation&] [s17; ullamco laboris nisi ut aliquip ex ea commodo consequat.&] [s17; Duis aute irure dolor in reprehenderit in voluptate velit&] [s17; esse cillum dolore eu fugiat nulla pariatur. Excepteur&] [s17; sint occaecat cupidatat non proident, sunt in culpa qui&] [s17; officia deserunt mollit anim id est laborum.&] [s0; &] [s5; U`+`+ streams provide no formatting capabilities (that is deferred to text utilities), but they have some unique features. U`+`+ does not distinguish between `'text`' and `'binary`' mode streams, methods are well suited to work with both in common mode.&] [s5; [*C@5 GetLine] returns [*C@5 String] of single line read (lines separator being `'`\n`', `'`\r`' is ignored):&] [s0; &] [s7; in.Seek(0);&] [s7; while(!in.IsEof())&] [s7; -|DUMP(in.GetLine());&] [s0; &] [s17; in.GetLine() `= Lorem ipsum dolor sit amet, consectetur adipiscing elit,&] [s17; in.GetLine() `= sed do eiusmod tempor incididunt ut labore et dolore magna&] [s17; in.GetLine() `= aliqua. Ut enim ad minim veniam, quis nostrud exercitation&] [s17; in.GetLine() `= ullamco laboris nisi ut aliquip ex ea commodo consequat.&] [s17; in.GetLine() `= Duis aute irure dolor in reprehenderit in voluptate velit&] [s17; in.GetLine() `= esse cillum dolore eu fugiat nulla pariatur. Excepteur&] [s17; in.GetLine() `= sint occaecat cupidatat non proident, sunt in culpa qui&] [s17; in.GetLine() `= officia deserunt mollit anim id est laborum.&] [s0; &] [s5; [*C@5 Peek] can be used to look at the next character without actually moving on to the next one:&] [s0; &] [s7; in.Seek(0);&] [s7; DDUMP((char)in.Peek());&] [s7; DDUMP(in.GetLine());&] [s0; &] [s17; (char)in.Peek() `= L&] [s17; in.GetLine() `= Lorem ipsum dolor sit amet, consectetur adipiscing elit,&] [s0; &] [s5; [*C@5 Get] method reads at most specified number of bytes from the stream and returns them as [*C@5 String]:&] [s0; &] [s7; in.Seek(0);&] [s7; DUMP(in.Get(10));&] [s0; &] [s17; in.Get(10) `= Lorem ipsu&] [s0; &] [s5; If there is not enough characters in the Stream as required by Get, everything till EOF is returned:&] [s0; &] [s7; in.Seek(0);&] [s7; DUMP(in.Get(999999).GetCount());&] [s0; &] [s17; in.Get(999999).GetCount() `= 452&] [s0; &] [s5; In contrast, [*C@5 GetAll] method fails when there is not enough characters in the Stream and returns Void [*C@5 String] if Stream is not in [*C@5 LoadThrowing] mode:&] [s0; &] [s7; in.Seek(0);&] [s7; h `= in.GetAll(100);&] [s7; DUMP(h.GetCount());&] [s0; &] [s17; h.GetCount() `= 100&] [s0; &] [s0; &] [s7; h `= in.GetAll(999999);&] [s7; DUMP(h.IsVoid());&] [s0; &] [s17; h.IsVoid() `= true&] [s0; &] [s5; In [*C@5 LoadThrowing] mode, [*C@5 Stream] throws [*C@5 LoadingError] exception when there is problem with input [*C@5 Stream]:&] [s0; &] [s7; in.LoadThrowing();&] [s7; try `{&] [s7; -|in.GetAll(999999);&] [s7; `}&] [s7; catch(LoadingError) `{&] [s7; -|LOG(`"Loading error`");&] [s7; `}&] [s0; &] [s17; Loading error&] [s0; &] [s5; Template variant of [*C@5 Stream`::operator<<] is using [*C@5 AsString] to convert data to text:&] [s0; &] [s7; String fn `= GetHomeDirFile(`"test.txt`");&] [s7; FileOut out(fn);&] [s7; if(!out) `{&] [s7; -|LOG(`"Failed to open the file`");&] [s7; -|return;&] [s7; `}&] [s7; out << `"Some number `" << 321 << `" and Point `" << Point(1, 2);&] [s7; out.Close();&] [s0; &] [s5; When writing to the [*C@5 Stream], the good way to check for errors is to write all data, close the stream and then check for [*C@5 IsError]:&] [s0; &] [s7; if(out.IsError()) `{ // check whether file was properly written&] [s7; -|LOG(`"Error`");&] [s7; -|return;&] [s7; `}&] [s7; DUMP(LoadFile(fn));&] [s0; &] [s17; LoadFile(fn) `= Some number 321 and Point `[1, 2`]&] [s0; &] [s5; [*C@5 FileAppend] can be used to append data to the file:&] [s0; &] [s7; FileAppend out2(fn);&] [s7; out2 << `"`\nSomething more`";&] [s7; out2.Close();&] [s7; DUMP(LoadFile(fn));&] [s0; &] [s17; LoadFile(fn) `= Some number 321 and Point `[1, 2`]&] [s17; Something more&] [s0; &] [s5; Important and often used type of [*C@5 Stream] is [*C@5 StringStream] which works with [*C@5 String] as input/output.&] [s5; [*C@5 Stream] also provides methods to store/load primitive types, in both little`-endian and big`-endian modes:&] [s0; &] [s7; StringStream ss;&] [s7; ss.Put32le(0x12345678);&] [s7; ss.Put32be(0x12345678);&] [s7; DUMPHEX(ss.GetResult());&] [s0; &] [s17; ss.GetResult() `= Memory at 0x0208fa48, size 0x8 `= 8&] [s17; `+0 0x0208FA48 78 56 34 12 12 34 56 78 xV4..4Vx &] [s0; &] [s0; &] [s7; StringStream ss2(ss.GetResult());&] [s7; DUMPHEX(ss2.Get32le());&] [s7; DUMPHEX(ss2.Get32be());&] [s0; &] [s17; ss2.Get32le() `= 0x12345678&] [s17; ss2.Get32be() `= 0x12345678&] [s0; &] [s3;H4;:Section`_2`_2: 2.2 Special streams&] [s5; [*C@5 SizeStream] counts the number of bytes written to the stream:&] [s0; &] [s7; SizeStream szs;&] [s7; szs << `"1234567`";&] [s7; DUMP(szs.GetSize());&] [s0; &] [s17; szs.GetSize() `= 7&] [s0; &] [s5; [*C@5 CompareStream] can be used to compare the content of some stream with data written to [*C@5 CompareStream]:&] [s0; &] [s7; StringStream in(`"123456`");&] [s7; CompareStream cs(in);&] [s7; cs.Put(`"12345`");&] [s7; DUMP(cs.IsEqual());&] [s0; &] [s17; cs.IsEqual() `= true&] [s0; &] [s0; &] [s7; cs.Put(`"7`");&] [s7; DUMP(cs.IsEqual());&] [s0; &] [s17; cs.IsEqual() `= false&] [s0; &] [s5; [*C@5 OutStream] buffers output data to bigger blocks, then outputs them via [*C@5 Out] virtual method:&] [s0; &] [s7; struct MyOutStream : OutStream `{&] [s7; -|virtual void Out(const void `*data, dword size) `{&] [s7; -|-|DUMPHEX(String((const char `*)data, size));&] [s7; -|`}&] [s7; `};&] [s7; &] [s7; MyOutStream os;&] [s7; os << `"This is a test `" << 12345;&] [s7; os.Close();&] [s0; &] [s17; String((const char `*)data, size) `= Memory at 0x07604a10, size 0x14 `= 20&] [s17; `+0 0x07604A10 54 68 69 73 20 69 73 20 61 20 74 65 73 74 20 31 This is a test 1&] [s17; `+16 0x07604A20 32 33 34 35 2345 &] [s0; &] [s5; [*C@5 TeeStream] sends output data to two separate streams:&] [s0; &] [s7; StringStream ss1;&] [s7; StringStream ss2;&] [s7; TeeStream tee(ss1, ss2);&] [s7; tee << `"Tee stream test`";&] [s7; tee.Close();&] [s7; DUMP(ss1.GetResult());&] [s7; DUMP(ss2.GetResult());&] [s0; &] [s17; ss1.GetResult() `= Tee stream test&] [s17; ss2.GetResult() `= Tee stream test&] [s0; &] [s5; [*C@5 MemReadStream] can be used to convert read`-only memory block to stream data:&] [s0; &] [s7; static const char s`[`] `= `"Some line`\nAnother line`";&] [s7; MemReadStream ms(s, sizeof(s) `- 1);&] [s7; while(!ms.IsEof())&] [s7; -|DUMPHEX(ms.GetLine());&] [s0; &] [s17; ms.GetLine() `= Memory at 0x0208f6f8, size 0x9 `= 9&] [s17; `+0 0x0208F6F8 53 6F 6D 65 20 6C 69 6E 65 Some line &] [s17; ms.GetLine() `= Memory at 0x0208f6f8, size 0xC `= 12&] [s17; `+0 0x0208F6F8 41 6E 6F 74 68 65 72 20 6C 69 6E 65 Another line &] [s0; &] [s3;H4;:Section`_2`_3: 2.3 Binary serialization&] [s5; Serialization is a mechanism that converts structured data to/from binary stream. In U`+`+, loading and storing of data is performed by single code, in most cases represented by method [*C@5 Serialize]. Serialization is performed directly with basic [*C@5 Stream]. To this end, [*C@5 Stream] features a single boolean representing the direction of serialization process. The direction can be checked using [*C@5 IsLoading] and [*C@5 IsStoring] methods and changed with [*C@5 SetStoring] and [*C@5 SetLoading] methods. Direction is usually set properly by derived classes (e.g. FileOut sets it to storing, FileIn to loading).&] [s5; Shortcut to calling [*C@5 Serialize] method is [*C@5 operator%], which is templated overload that calls [*C@5 Serialize] for given variable (primitive types have direct overload in [*C@5 Stream] class):&] [s0; &] [s7; StringStream ss;&] [s7; &] [s7; int x `= 123;&] [s7; Color h `= White();&] [s7; &] [s7; ss % x % h;&] [s7; &] [s7; StringStream ss2(ss.GetResult());&] [s7; &] [s7; int x2;&] [s7; Color h2;&] [s7; &] [s7; ss2 % x2 % h2;&] [s7; &] [s7; DUMP(x2);&] [s7; DUMP(h2);&] [s0; &] [s17; x2 `= 123&] [s17; h2 `= Color(255, 255, 255)&] [s0; &] [s5; When serialization fails to load the data (e.g. because of wrong structure or not enough data in the stream), [*C@5 Stream`::LoadError] is invoked, which can trigger the exception if the stream is [*C@5 LoadThrowing]:&] [s0; &] [s7; ss2.Seek(0);&] [s7; ss2.LoadThrowing();&] [s7; try `{&] [s7; -|ss2 % x2 % h2 % x2;&] [s7; `}&] [s7; catch(LoadingError) `{&] [s7; -|LOG(`"Deserialization has failed`");&] [s7; `}&] [s0; &] [s17; Deserialization has failed&] [s0; &] [s5; Examples so far serve mostly like basic demonstration of serialization. In practice, the implementation is usually represented by [*C@5 Serialize] method of class that is to be compatible with this concept. To that end, it is a good idea to provide means for future expansion of such class:&] [s0; &] [s7; struct MyFoo `{&] [s7; -|int number;&] [s7; -|Color color;&] [s7; -|&] [s7; -|void Serialize(Stream`& s) `{&] [s7; -|-|int version `= 0;&] [s7; -|-|s / version; // allow backward compatibility in the future&] [s7; -|-|s.Magic(31415); // put magic number into the stream to check for invalid data&] [s7; -|-|s % number % color;&] [s7; -|`}&] [s7; `};&] [s7; &] [s7; MyFoo foo;&] [s7; foo.number `= 321;&] [s7; foo.color `= Blue();&] [s0; &] [s5; [*C@5 StoreAsFile], [*C@5 StoreAsString], [*C@5 LoadFromFile] and [*C@5 LoadFromString] are convenience functions that simplify storing / loading objects to / from the most common forms of storage:&] [s0; &] [s7; String data `= StoreAsString(foo);&] [s7; MyFoo foo2;&] [s7; LoadFromString(foo2, data);&] [s7; DUMP(foo2.number);&] [s7; DUMP(foo2.color);&] [s0; &] [s17; foo2.number `= 321&] [s17; foo2.color `= Color(0, 0, 128)&] [s0; &] [s5; Now if [*C@5 MyFoo] was to be extended to [*C@5 MyFoo2] and we wanted to maintain the ability to load it from binary data stored by original [*C@5 MyFoo], we can branch on previously stored [*C@5 version]:&] [s0; &] [s7; struct MyFoo2 `{&] [s7; -|int number;&] [s7; -|Color color;&] [s7; -|String text;&] [s7; -|&] [s7; -|void Serialize(Stream`& s) `{&] [s7; -|-|int version `= 1;&] [s7; -|-|s / version;&] [s7; -|-|s % number % color;&] [s7; -|-|if(version >`= 1)&] [s7; -|-|-|s % text;&] [s7; -|`}&] [s7; `};&] [s7; MyFoo2 foo3;&] [s7; LoadFromString(foo3, data);&] [s7; DUMP(foo3.number);&] [s7; DUMP(foo3.color);&] [s0; &] [s17; foo3.number `= 0&] [s17; foo3.color `= Color(Null)&] [s0; &] [s5; Note: [*C@5 operator/] is Stream method with several overloads optimized for small value `- in this case [*C@5 int] is stored as single byte if possible (and as 5 bytes if not).&] [s22;:Chapter`_3: 3. Array containers&] [s3;:Section`_3`_1: 3.1 [C@5 Vector] basics&] [s5; [*C@5 Vector] is the basic container of U`+`+. It is the random access container similar to [*C@5 std`::vector] with one important performance related difference: There are rules for elements of [*C@5 Vector] that allow its implementation to move elements in memory using plain [*C@5 memcpy]/``memmove`` (`"Moveable`" concept).&] [s5; Anyway, for now let us start with simple [*C@5 Vector] of [*C@5 int]s:&] [s0; &] [s7; -|Vector v;&] [s0; &] [s5; You can add elements to the Vector as parameters of the Add method&] [s0; &] [s7; -|v.Add(1);&] [s7; -|v.Add(2);&] [s7; -|&] [s7; -|DUMP(v);&] [s0; &] [s17; v `= `[1, 2`]&] [s0; &] [s5; Alternative and very important possibility for U`+`+ containers is `'in`-place creation`'. In this case, parameter`-less Add returns a reference to a new element in [*C@5 Vector]:&] [s0; &] [s7; -|v.Add() `= 3;&] [s7; -|&] [s7; -|DUMP(v);&] [s0; &] [s17; v `= `[1, 2, 3`]&] [s0; &] [s5; You can also use [*C@5 operator<<]&] [s0; &] [s7; -|v << 4 << 5;&] [s7; &] [s7; -|DUMP(v);&] [s0; &] [s17; v `= `[1, 2, 3, 4, 5`]&] [s0; &] [s5; [*C@5 Vector] also supports initializer lists:&] [s0; &] [s7; -|v.Append(`{ 6, 7 `});&] [s7; &] [s7; -|DUMP(v);&] [s0; &] [s17; v `= `[1, 2, 3, 4, 5, 6, 7`]&] [s0; &] [s5; To iterate [*C@5 Vector] you can use indices:&] [s0; &] [s7; -|for(int i `= 0; i < v.GetCount(); i`+`+)&] [s7; -|-|LOG(v`[i`]);&] [s0; &] [s17; 1&] [s17; 2&] [s17; 3&] [s17; 4&] [s17; 5&] [s17; 6&] [s17; 7&] [s0; &] [s5; begin/end interface:&] [s0; &] [s7; -|for(auto q `= v.begin(), e `= v.end(); q !`= e; q`+`+)&] [s7; -|-|LOG(`*q);&] [s0; &] [s17; 1&] [s17; 2&] [s17; 3&] [s17; 4&] [s17; 5&] [s17; 6&] [s17; 7&] [s0; &] [s5; C`+`+11 range`-for syntax:&] [s0; &] [s7; -|for(const auto`& q : v)&] [s7; -|-|LOG(q);&] [s0; &] [s17; 1&] [s17; 2&] [s17; 3&] [s17; 4&] [s17; 5&] [s17; 6&] [s17; 7&] [s0; &] [s3;H4;:Section`_3`_2: 3.2 [C@5 Vector] operations&] [s5; You can [*C@5 Insert] or [*C@5 Remove] elements at random positions of Vector (O(n) complexity):&] [s0; &] [s7; Vector v;&] [s7; v.Add(1);&] [s7; v.Add(2);&] [s7; &] [s7; v.Insert(1, 10);&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[1, 10, 2`]&] [s0; &] [s0; &] [s7; v.Insert(0, `{ 7, 6, 5 `});&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[7, 6, 5, 1, 10, 2`]&] [s0; &] [s0; &] [s7; v.Remove(0);&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[6, 5, 1, 10, 2`]&] [s0; &] [s5; [*C@5 At] method returns element at specified position ensuring that such a position exists. If there is not enough elements in [*C@5 Vector], required number of elements is added. If second parameter of [*C@5 At] is present, newly added elements are initialized to this value.&] [s0; &] [s7; v.Clear();&] [s7; for(int i `= 0; i < 10000; i`+`+)&] [s7; -|v.At(Random(10), 0)`+`+;&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[922, 995, 1050, 1007, 1002, 998, 1020, 1023, 1000, 983`]&] [s0; &] [s5; Referencing invalid index is undefined operation. Sometimes however it is useful to return the element value if index is valid and some default value if it is not. This can be achieved with two parameter Get method:&] [s0; &] [s7; DUMP(v.Get(4, 0));&] [s7; DUMP(v.Get(`-10, 0));&] [s7; DUMP(v.Get(13, `-1));&] [s0; &] [s17; v.Get(4, 0) `= 1002&] [s17; v.Get(`-10, 0) `= 0&] [s17; v.Get(13, `-1) `= `-1&] [s0; &] [s5; You can apply algorithms on containers, e.g. Sort&] [s0; &] [s7; Sort(v);&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[922, 983, 995, 998, 1000, 1002, 1007, 1020, 1023, 1050`]&] [s0; &] [s3;H4;:Section`_3`_3: 3.3 Transfer issues&] [s5; Often you need to pass content of one container to another of the same type. U`+`+ containers always support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ p ick semantics] (synonym of std`::move), and, depending on type stored, also might support [^topic`:`/`/Core`/srcdoc`/pick`_`$en`-us^ clone semantics]. When transferring the value, you have to explicitly specify which one to use:&] [s0; &] [s7; Vector v`{ 1, 2 `};&] [s7; &] [s7; DUMP(v);&] [s7; &] [s7; Vector v1 `= pick(v);&] [s7; &] [s7; DUMP(v);&] [s7; DUMP(v1);&] [s0; &] [s17; v `= `[1, 2`]&] [s17; v `= `[`]&] [s17; v1 `= `[1, 2`]&] [s0; &] [s5; now source [*C@5 Vector] [*C@5 v] is empty, as elements were `'picked`' to [*C@5 v1].&] [s5; If you really need to preserve value of source (and elements support deep copy operation), you can use [*C@5 clone]:&] [s0; &] [s7; v `= clone(v1);&] [s7; &] [s7; DUMP(v);&] [s7; DUMP(v1);&] [s0; &] [s17; v `= `[1, 2`]&] [s17; v1 `= `[1, 2`]&] [s0; &] [s5; The requirement of explicit [*C@5 clone] has the advantage of avoiding unexpected deep copies. For example:&] [s0; &] [s7; Vector> x;&] [s7; x.Add() << 1 << 2 << 3;&] [s7; &] [s7; for(auto i : x) `{ LOG(i); `}&] [s0; &] [s5; results in run`-time error, whereas the equivalent code with [*C@5 std`::vector] compiles but silently performs deep copy for each iteration:&] [s0; &] [s7; std`::vector> sv;&] [s7; sv.push`_back(`{1, 2, 3`});&] [s7; for(auto i : sv) // invokes std`::vector copy constructor&] [s7; -|for(auto j : i)&] [s7; -|-|DUMP(j);&] [s0; &] [s5; That said, in certain cases it is simpler to have default copy instead of explicit [*C@5 clone]. You can easily achieve that using [*C@5 WithDeepCopy] template:&] [s0; &] [s7; WithDeepCopy> v2;&] [s7; &] [s7; v2 `= v;&] [s7; &] [s7; DUMP(v);&] [s7; DUMP(v2);&] [s0; &] [s17; v `= `[1, 2`]&] [s17; v2 `= `[1, 2`]&] [s0; &] [s3;H4;:Section`_3`_4: 3.4 Client types in U`+`+ containers&] [s5; So far we were using int as type of elements. In order to store client defined types into the [*C@5 Vector] (and the Vector [^topic`:`/`/Core`/src`/Overview`$en`-us^ f lavor]) the type must satisfy [^topic`:`/`/Core`/src`/Moveable`$en`-us^ moveable] requirement `- in short, it must not contain back`-pointers nor virtual methods. Type must be marked as [/ moveable] in order to define interface contract using [*C@5 Moveable] [^https`:`/`/en`.wikipedia`.org`/wiki`/Curiously`_recurring`_template`_pattern^ C RTP idiom]:&] [s0; &] [s7; struct Distribution : Moveable `{&] [s7; -|String text;&] [s7; -|Vector data;&] [s7; -|&] [s7; -|String ToString() const `{ return text `+ `": `" `+ AsString(data); `}&] [s7; `};&] [s0; &] [s5; Now to add [*C@5 Distribution] elements you cannot use [*C@5 Vector`::Add(const T`&)], because it requires elements to have default deep`-copy constructor `- and [*C@5 Distribution does not have one, as ]Vector`` has default pick`-constructor, so Distribution itself has pick`-constructor. It would no be a good idea either, because deep`-copy would involve expensive copying of inner Vector.&] [s5; Instead, Add without parameters has to be used `- it default constructs (that is cheap) element in Vector and returns reference to it:&] [s0; &] [s7; Vector dist;&] [s7; for(int n `= 5; n <`= 10; n`+`+) `{&] [s7; -|Distribution`& d `= dist.Add();&] [s7; -|d.text << `"Test `" << n;&] [s7; -|for(int i `= 0; i < 10000; i`+`+)&] [s7; -|-|d.data.At(Random(n), 0)`+`+;&] [s7; `}&] [s7; &] [s7; DUMPC(dist);&] [s0; &] [s17; dist:&] [s17; -|`[0`] `= Test 5: `[2006, 2009, 2025, 1958, 2002`]&] [s17; -|`[1`] `= Test 6: `[1691, 1660, 1665, 1664, 1633, 1687`]&] [s17; -|`[2`] `= Test 7: `[1433, 1400, 1413, 1426, 1429, 1476, 1423`]&] [s17; -|`[3`] `= Test 8: `[1266, 1272, 1139, 1267, 1263, 1233, 1289, 1271`]&] [s17; -|`[4`] `= Test 9: `[1076, 1127, 1132, 1129, 1155, 1089, 1045, 1114, 1133`]&] [s17; -|`[5`] `= Test 10: `[998, 995, 1012, 973, 1003, 1000, 1009, 1010, 991, 1009`]&] [s0; &] [s5; Another possibility is to use [*C@5 Vector`::Add(T`&`&)] method, which uses pick`-constructor instead of deep`-copy constructor. E.g. [*C@5 Distribution] elements might be generated by some function:&] [s0; &] [s7; Distribution CreateDist(int n);&] [s0; &] [s5; and code for adding such elements to Vector then looks like:&] [s0; &] [s7; for(n `= 5; n <`= 10; n`+`+)&] [s7; -|dist.Add(CreateDist(n));&] [s0; &] [s5; alternatively, you can use default`-constructed variant too&] [s0; &] [s7; -|dist.Add() `= CreateDist();&] [s0; &] [s3;H4;:Section`_3`_5: 3.5 Array flavor&] [s5; If elements are not [*C@5 Moveable] and therefore cannot be stored in [*C@5 Vector] flavor, they can still be stored in [*C@5 Array] flavor. Another reason for using Array is the need for referencing elements `- Array flavor never invalidates references or pointers to them. Finally, if sizeof(T) is large (say more than 100`-200 bytes), using Array might be better from performance perspective.&] [s5; Example of elements that cannot be stored in Vector flavor are standard library objects like [*C@5 std`::string] (because obviously, standard library knows nothing about U`+`+ Moveable concept):&] [s0; &] [s7; Array as;&] [s7; for(int i `= 0; i < 4; i`+`+)&] [s7; -|as.Add(`"Test`");&] [s7; &] [s7; for(auto s : as)&] [s7; -|DUMP(s.c`_str());&] [s0; &] [s17; s.c`_str() `= Test&] [s17; s.c`_str() `= Test&] [s17; s.c`_str() `= Test&] [s17; s.c`_str() `= Test&] [s0; &] [s3;H4;:Section`_3`_6: 3.6 Polymorphic [C@5 Array]&] [s5; [*C@5 Array] can even be used for storing polymorphic elements:&] [s0; &] [s7; struct Number `{&] [s7; -|virtual double Get() const `= 0;&] [s7; -|String ToString() const `{ return AsString(Get()); `}&] [s7; -|virtual `~Number() `{`}&] [s7; `};&] [s7; &] [s7; struct Integer : public Number `{&] [s7; -|int n;&] [s7; -|virtual double Get() const `{ return n; `}&] [s7; `};&] [s7; &] [s7; struct Double : public Number `{&] [s7; -|double n;&] [s7; -|virtual double Get() const `{ return n; `}&] [s7; `};&] [s0; &] [s5; To add such derived types to [*C@5 Array], you can best use in`-place creation with [*C@5 Create] method:&] [s0; &] [s7; Array num;&] [s7; num.Create().n `= 15.5;&] [s7; num.Create().n `= 3;&] [s7; &] [s7; DUMP(num);&] [s0; &] [s17; num `= `[15.5, 3`]&] [s0; &] [s5; Alternatively, you can use [*C@5 Add(T `*)] method and provide a pointer to the newly created instance on the heap ([*C@5 Add] returns a reference to the instance):&] [s0; &] [s7; Double `*nd `= new Double;&] [s7; nd`->n `= 1.1;&] [s7; num.Add(nd);&] [s7; &] [s7; DUMP(num);&] [s0; &] [s17; num `= `[15.5, 3, 1.1`]&] [s0; &] [s5; Array takes ownership of heap object and deletes it as appropriate. We recommend to use this variant only if in`-place creation with [*C@5 Create] is not possible.&] [s5; It is OK do directly apply U`+`+ algorithms on [*C@5 Array] (the most stringent requirement of any of basic algorithms is that there is [*C@5 IterSwap] provided for container iterators and that is specialized for [*C@5 Array] iterators):&] [s0; &] [s7; Sort(num, `[`](const Number`& a, const Number`& b) `{ return a.Get() < b.Get(); `});&] [s7; &] [s7; DUMP(num);&] [s0; &] [s17; num `= `[1.1, 3, 15.5`]&] [s0; &] [s3;H4;:Section`_3`_7: 3.7 Bidirectional containers&] [s5; [*C@5 Vector] and [*C@5 Array] containers allow fast adding and removing elements at the end of sequence. Sometimes, same is needed at begin of sequence too (usually to support FIFO queues). [*C@5 BiVector] and [*C@5 BiArray] are optimal for this scenario:&] [s0; &] [s7; BiVector n;&] [s7; n.AddHead(1);&] [s7; n.AddTail(2);&] [s7; n.AddHead(3);&] [s7; n.AddTail(4);&] [s7; DUMP(n);&] [s0; &] [s17; n `= `[3, 1, 2, 4`]&] [s0; &] [s0; &] [s7; n.DropHead();&] [s7; DUMP(n);&] [s0; &] [s17; n `= `[1, 2, 4`]&] [s0; &] [s0; &] [s7; n.DropTail();&] [s7; DUMP(n);&] [s0; &] [s17; n `= `[1, 2`]&] [s0; &] [s0; &] [s7; struct Val `{&] [s7; -|virtual String ToString() const `= 0;&] [s7; -|virtual `~Val() `{`}&] [s7; `};&] [s7; &] [s7; struct Number : Val `{&] [s7; -|int n;&] [s7; -|virtual String ToString() const `{ return AsString(n); `}&] [s7; `};&] [s7; &] [s7; struct Text : Val `{&] [s7; -|String s;&] [s7; -|virtual String ToString() const `{ return s; `}&] [s7; `};&] [s7; &] [s7; BiArray num;&] [s7; num.CreateHead().n `= 3;&] [s7; num.CreateTail().s `= `"Hello`";&] [s7; num.CreateHead().s `= `"World`";&] [s7; num.CreateTail().n `= 2;&] [s7; &] [s7; DUMP(num);&] [s0; &] [s17; num `= `[World, 3, Hello, 2`]&] [s0; &] [s3;H4;:Section`_3`_8: 3.8 [C@5 Index]&] [s5; [*C@5 Index] is the the foundation of all U`+`+ associative operations and is one of defining features of U`+`+.&] [s5; [*C@5 Index] is a container very similar to the plain [*C@5 Vector] (it is random access array of elements with fast addition at the end) with one additional feature `- it is able to fast retrieve position of element with required value using [*C@5 Find] method:&] [s0; &] [s7; Index ndx;&] [s7; ndx.Add(`"alfa`");&] [s7; ndx.Add(`"beta`");&] [s7; ndx.Add(`"gamma`");&] [s7; ndx.Add(`"delta`");&] [s7; ndx.Add(`"kappa`");&] [s7; &] [s7; DUMP(ndx);&] [s7; DUMP(ndx.Find(`"beta`"));&] [s0; &] [s17; ndx `= `[alfa, beta, gamma, delta, kappa`]&] [s17; ndx.Find(`"beta`") `= 1&] [s0; &] [s5; If element is not present in [*C@5 Index], [*C@5 Find] returns a negative value:&] [s0; &] [s7; DUMP(ndx.Find(`"something`"));&] [s0; &] [s17; ndx.Find(`"something`") `= `-1&] [s0; &] [s5; Any element can be replaced using [*C@5 Set] method:&] [s0; &] [s7; ndx.Set(1, `"alfa`");&] [s7; &] [s7; DUMP(ndx);&] [s0; &] [s17; ndx `= `[alfa, alfa, gamma, delta, kappa`]&] [s0; &] [s5; If there are more elements with the same value, they can be iterated using [*C@5 FindNext] method:&] [s0; &] [s7; int fi `= ndx.Find(`"alfa`");&] [s7; while(fi >`= 0) `{&] [s7; -|DUMP(fi);&] [s7; -|fi `= ndx.FindNext(fi);&] [s7; `}&] [s0; &] [s17; fi `= 0&] [s17; fi `= 1&] [s0; &] [s5; [*C@5 FindAdd] method retrieves position of element like [*C@5 Find], but if element is not present in [*C@5 Index], it is added:&] [s0; &] [s7; DUMP(ndx.FindAdd(`"one`"));&] [s7; DUMP(ndx.FindAdd(`"two`"));&] [s7; DUMP(ndx.FindAdd(`"three`"));&] [s7; DUMP(ndx.FindAdd(`"two`"));&] [s7; DUMP(ndx.FindAdd(`"three`"));&] [s7; DUMP(ndx.FindAdd(`"one`"));&] [s0; &] [s17; ndx.FindAdd(`"one`") `= 5&] [s17; ndx.FindAdd(`"two`") `= 6&] [s17; ndx.FindAdd(`"three`") `= 7&] [s17; ndx.FindAdd(`"two`") `= 6&] [s17; ndx.FindAdd(`"three`") `= 7&] [s17; ndx.FindAdd(`"one`") `= 5&] [s0; &] [s5; Removing elements from random access sequence tends to be expensive, that is why rather than remove, [*C@5 Index] supports [*C@5 Unlink] and [*C@5 UnlinkKey] operations, which retain the element in [*C@5 Index] but make it invisible for [*C@5 Find] operation:&] [s0; &] [s7; ndx.Unlink(2);&] [s7; ndx.UnlinkKey(`"kappa`");&] [s7; &] [s7; DUMP(ndx.Find(ndx`[2`]));&] [s7; DUMP(ndx.Find(`"kappa`"));&] [s0; &] [s17; ndx.Find(ndx`[2`]) `= `-1&] [s17; ndx.Find(`"kappa`") `= `-1&] [s0; &] [s5; You can test whether element at given position is unlinked using [*C@5 IsUnlinked] method&] [s0; &] [s7; DUMP(ndx.IsUnlinked(1));&] [s7; DUMP(ndx.IsUnlinked(2));&] [s0; &] [s17; ndx.IsUnlinked(1) `= false&] [s17; ndx.IsUnlinked(2) `= true&] [s0; &] [s5; Unlinked positions can be reused by [*C@5 Put] method:&] [s0; &] [s7; ndx.Put(`"foo`");&] [s7; &] [s7; DUMP(ndx);&] [s7; DUMP(ndx.Find(`"foo`"));&] [s0; &] [s17; ndx `= `[alfa, alfa, gamma, delta, foo, one, two, three`]&] [s17; ndx.Find(`"foo`") `= 4&] [s0; &] [s5; You can also remove all unlinked elements from [*C@5 Index] using [*C@5 Sweep] method:&] [s0; &] [s7; ndx.Sweep();&] [s7; &] [s7; DUMP(ndx);&] [s0; &] [s17; ndx `= `[alfa, alfa, delta, foo, one, two, three`]&] [s0; &] [s5; Operations directly removing or inserting elements of Index are expensive, but available too:&] [s0; &] [s7; ndx.Remove(1);&] [s7; &] [s7; DUMP(ndx);&] [s0; &] [s17; ndx `= `[alfa, delta, foo, one, two, three`]&] [s0; &] [s0; &] [s7; ndx.RemoveKey(`"two`");&] [s7; &] [s7; DUMP(ndx);&] [s0; &] [s17; ndx `= `[alfa, delta, foo, one, three`]&] [s0; &] [s0; &] [s7; ndx.Insert(0, `"insert`");&] [s7; &] [s7; DUMP(ndx);&] [s0; &] [s17; ndx `= `[insert, alfa, delta, foo, one, three`]&] [s0; &] [s5; PickKeys operation allows you to obtain Vector of elements of Index in low constant time operation (while destroying source Index)&] [s0; &] [s7; Vector d `= ndx.PickKeys();&] [s7; &] [s7; DUMP(d);&] [s0; &] [s17; d `= `[insert, alfa, delta, foo, one, three`]&] [s0; &] [s5; Pick`-assigning [*C@5 Vector] to [*C@5 Index] is supported as well:&] [s0; &] [s7; d`[0`] `= `"test`";&] [s7; &] [s7; ndx `= pick(d);&] [s7; &] [s7; DUMP(ndx);&] [s0; &] [s17; ndx `= `[test, alfa, delta, foo, one, three`]&] [s0; &] [s3;H4;:Section`_3`_9: 3.9 Index and client types&] [s5; In order to store elements to [*C@5 Index], they type must be [*C@5 Moveable], have deep copy and defined the [*C@5 operator`=`=] and a [*C@5 GetHashValue] function or method to compute the hash code. It is recommended to use [*C@5 CombineHash] to combine hash values of types that already provide [*C@5 GetHashValue]:&] [s0; &] [s7; struct Person : Moveable `{&] [s7; -|String name;&] [s7; -|String surname;&] [s7; &] [s7; -|unsigned GetHashValue() const `{ return CombineHash(name, surname); `}&] [s7; -|bool operator`=`=(const Person`& b) const `{ return name `=`= b.name `&`& surname `=`= b.surname; `}&] [s7; &] [s7; -|Person(String name, String surname) : name(name), surname(surname) `{`}&] [s7; -|Person() `{`}&] [s7; `};&] [s7; &] [s7; Index p;&] [s7; p.Add(Person(`"John`", `"Smith`"));&] [s7; p.Add(Person(`"Paul`", `"Carpenter`"));&] [s7; p.Add(Person(`"Carl`", `"Engles`"));&] [s7; &] [s7; DUMP(p.Find(Person(`"Paul`", `"Carpenter`")));&] [s0; &] [s17; p.Find(Person(`"Paul`", `"Carpenter`")) `= 1&] [s0; &] [s3;H4;:Section`_3`_10: 3.10 [C@5 VectorMap], [C@5 ArrayMap]&] [s5; [*C@5 VectorMap] is nothing else than a simple composition of [*C@5 Index] of keys and [*C@5 Vector] of values. You can use [*C@5 Add] methods to put elements into the [*C@5 VectorMap]:&] [s0; &] [s7; struct Person : Moveable `{&] [s7; -|String name;&] [s7; -|String surname;&] [s7; -|&] [s7; -|String ToString() const `{ return String() << name << `' `' << surname; `}&] [s7; &] [s7; -|Person(String name, String surname) : name(name), surname(surname) `{`}&] [s7; -|Person() `{`}&] [s7; `};&] [s7; &] [s7; VectorMap m;&] [s7; &] [s7; m.Add(`"1`", Person(`"John`", `"Smith`"));&] [s7; m.Add(`"2`", Person(`"Carl`", `"Engles`"));&] [s7; &] [s7; Person`& p `= m.Add(`"3`");&] [s7; p.name `= `"Paul`";&] [s7; p.surname `= `"Carpenter`";&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{1: John Smith, 2: Carl Engles, 3: Paul Carpenter`}&] [s0; &] [s5; [*C@5 VectorMap] provides read`-only access to its [*C@5 Index] of keys and read`-write access to its [*C@5 Vector] of values:&] [s0; &] [s7; DUMP(m.GetKeys());&] [s7; DUMP(m.GetValues());&] [s0; &] [s17; m.GetKeys() `= `[1, 2, 3`]&] [s17; m.GetValues() `= `[John Smith, Carl Engles, Paul Carpenter`]&] [s0; &] [s0; &] [s7; m.GetValues()`[2`].name `= `"Peter`";&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{1: John Smith, 2: Carl Engles, 3: Peter Carpenter`}&] [s0; &] [s5; You can use indices to iterate [*C@5 VectorMap] contents:&] [s0; &] [s7; for(int i `= 0; i < m.GetCount(); i`+`+)&] [s7; -|LOG(m.GetKey(i) << `": `" << m`[i`]);&] [s0; &] [s17; 1: John Smith&] [s17; 2: Carl Engles&] [s17; 3: Peter Carpenter&] [s0; &] [s5; Standard [*C@5 begin] / [*C@5 end] pair for [*C@5 VectorMap] is the range of just values (internal Vector) `- it corresponds with [*C@5 operator`[`]] returning values:&] [s0; &] [s7; for(const auto`& p : m)&] [s7; -|DUMP(p);&] [s0; &] [s17; p `= John Smith&] [s17; p `= Carl Engles&] [s17; p `= Peter Carpenter&] [s0; &] [s5; To iterate through keys, you can use [*C@5 begin]/``end`` of internal [*C@5 Index]:&] [s0; &] [s7; for(const auto`& p : m.GetKeys())&] [s7; -|DUMP(p);&] [s0; &] [s17; p `= 1&] [s17; p `= 2&] [s17; p `= 3&] [s0; &] [s5; Alternatively, it is possible to create `'projection range`' of VectorMap that provides convenient key/value iteration, using [*C@5 operator`~] (note that is also removes `'unlinked`' items, see later):&] [s0; &] [s7; for(const auto`& e : `~m) `{&] [s7; -|DUMP(e.key);&] [s7; -|DUMP(e.value);&] [s7; `}&] [s0; &] [s17; e.key `= 1&] [s17; e.value `= John Smith&] [s17; e.key `= 2&] [s17; e.value `= Carl Engles&] [s17; e.key `= 3&] [s17; e.value `= Peter Carpenter&] [s0; &] [s5; Note that the `'projection range`' obtained by [*C@5 operator`~] is temporary value, which means that if mutating operation is required for values, r`-value reference has to be used instead of plain reference:&] [s0; &] [s7; for(const auto`& e : `~m)&] [s7; -|if(e.key `=`= `"2`")&] [s7; -|-|e.value.surname `= `"May`";&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{1: John Smith, 2: Carl May, 3: Peter Carpenter`}&] [s0; &] [s5; You can use Find method to retrieve position of element with required key:&] [s0; &] [s7; DUMP(m.Find(`"2`"));&] [s0; &] [s17; m.Find(`"2`") `= 1&] [s0; &] [s5; or Get method to retrieve corresponding value:&] [s0; &] [s7; DUMP(m.Get(`"2`"));&] [s0; &] [s17; m.Get(`"2`") `= Carl May&] [s0; &] [s5; Passing key not present in [*C@5 VectorMap] as [*C@5 Get] parameter is undefined behavior (ASSERT fails in debug mode), but there exists two parameter version of [*C@5 Get] that returns second parameter if the key is not found in VectorMap:&] [s0; &] [s7; DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&] [s0; &] [s17; m.Get(`"33`", Person(`"unknown`", `"person`")) `= unknown person&] [s0; &] [s5; As with [*C@5 Index], you can use [*C@5 Unlink] to make elements invisible for Find operations:&] [s0; &] [s7; m.Unlink(1);&] [s7; DUMP(m.Find(`"2`"));&] [s0; &] [s17; m.Find(`"2`") `= `-1&] [s0; &] [s5; [*C@5 SetKey] changes the key of the element:&] [s0; &] [s7; m.SetKey(1, `"33`");&] [s7; DUMP(m.Get(`"33`", Person(`"unknown`", `"person`")));&] [s0; &] [s17; m.Get(`"33`", Person(`"unknown`", `"person`")) `= Carl May&] [s0; &] [s5; If there are more elements with the same key in [*C@5 VectorMap], you can iterate them using [*C@5 FindNext] method:&] [s0; &] [s7; m.Add(`"33`", Person(`"Peter`", `"Pan`"));&] [s7; &] [s7; int q `= m.Find(`"33`");&] [s7; while(q >`= 0) `{&] [s7; -|DUMP(m`[q`]);&] [s7; -|q `= m.FindNext(q);&] [s7; `}&] [s0; &] [s17; m`[q`] `= Carl May&] [s17; m`[q`] `= Peter Pan&] [s0; &] [s5; Unlinked positions can be `'reused`' using Put method:&] [s0; &] [s7; m.UnlinkKey(`"33`");&] [s7; m.Put(`"22`", Person(`"Ali`", `"Baba`"));&] [s7; m.Put(`"44`", Person(`"Ivan`", `"Wilks`"));&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{1: John Smith, 44: Ivan Wilks, 3: Peter Carpenter, 22: Ali Baba`}&] [s0; &] [s5; [*C@5 PickValues] / [*C@5 PickIndex] / [*C@5 PickKeys] / pick internal [*C@5 Vector] / [*C@5 Index] / [*C@5 Vector] of [*C@5 Index]:&] [s0; &] [s7; Vector ps `= m.PickValues();&] [s7; Vector ks `= m.PickKeys();&] [s7; &] [s7; DUMP(ps);&] [s7; DUMP(ks);&] [s7; DUMP(m);&] [s0; &] [s17; ps `= `[John Smith, Ivan Wilks, Peter Carpenter, Ali Baba`]&] [s17; ks `= `[1, 44, 3, 22`]&] [s17; m `= `{`}&] [s0; &] [s5; [*C@5 VectorMap] pick constructor to create map by picking:&] [s0; &] [s7; ks`[0`] `= `"Changed key`";&] [s7; &] [s7; m `= VectorMap(pick(ks), pick(ps));&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{Changed key: John Smith, 44: Ivan Wilks, 3: Peter Carpenter, 22: Ali Baba`}&] [s0; &] [s5; [*C@5 ArrayMap] is composition of Index and Array, for cases where Array is better fit for value type (e.g. they are polymorphic):&] [s0; &] [s7; ArrayMap am;&] [s7; am.Create(`"key`", `"new`", `"person`");&] [s7; &] [s7; DUMP(am);&] [s0; &] [s17; am `= `{key: new person`}&] [s0; &] [s3;H4;:Section`_3`_11: 3.11 [C@5 One]&] [s5; [*C@5 One] is a container that can store none or one element of T or derived from T. It is functionally quite similar to [*C@5 std`::unique`_ptr], but has some convenient features.&] [s0; &] [s7; struct Base `{&] [s7; -|virtual String Get() `= 0;&] [s7; -|virtual `~Base() `{`}&] [s7; `};&] [s7; &] [s7; struct Derived1 : Base `{&] [s7; -|virtual String Get() `{ return `"Derived1`"; `}&] [s7; `};&] [s7; &] [s7; struct Derived2 : Base `{&] [s7; -|virtual String Get() `{ return `"Derived2`"; `}&] [s7; `};&] [s7; &] [s7; One s;&] [s0; &] [s5; [*C@5 operator bool] of one returns true if it contains an element:&] [s0; &] [s7; DUMP((bool)s);&] [s0; &] [s17; (bool)s `= false&] [s0; &] [s0; &] [s7; s.Create();&] [s7; DUMP((bool)s);&] [s7; DUMP(s`->Get());&] [s0; &] [s17; (bool)s `= true&] [s17; s`->Get() `= Derived1&] [s0; &] [s5; You can use [*C@5 Is] to check if certain type is currently stored in [*C@5 One]:&] [s0; &] [s7; DUMP(s.Is());&] [s7; DUMP(s.Is());&] [s7; DUMP(s.Is());&] [s0; &] [s17; s.Is() `= true&] [s17; s.Is() `= true&] [s17; s.Is() `= false&] [s0; &] [s5; To get a pointer to the contained instance, use [*C@5 operator`~]:&] [s0; &] [s7; Base `*b `= `~s;&] [s7; DUMP(b`->Get());&] [s0; &] [s17; b`->Get() `= Derived1&] [s0; &] [s5; Clear method removes the element from One:&] [s0; &] [s7; s.Clear();&] [s7; DUMP((bool)s);&] [s0; &] [s17; (bool)s `= false&] [s0; &] [s5; Helper function MakeOne derived from One can be used to create contained element:&] [s0; &] [s7; s `= MakeOne();&] [s7; DUMP(s`->Get());&] [s0; &] [s17; s`->Get() `= Derived1&] [s0; &] [s0; &] [s7; auto t `= pick(s);&] [s7; DUMP(t`->Get());&] [s0; &] [s17; t`->Get() `= Derived1&] [s0; &] [s3;H4;:Section`_3`_12: 3.12 [C@5 Any]&] [s5; [*C@5 Any] is a container that can contain none or one element of [/ any] type. [*C@5 Any`::Is] method matches exact type ignoring class hierarchies (unlike [*C@5 One`::Is]). You can use [*C@5 Get] to retrieve a reference to the instance stored:&] [s0; &] [s7; for(int pass `= 0; pass < 2; pass`+`+) `{&] [s7; -|Any x;&] [s7; -|if(pass)&] [s7; -|-|x.Create() `= `"Hello!`";&] [s7; -|else&] [s7; -|-|x.Create() `= Blue();&] [s7; -|&] [s7; -|if(x.Is())&] [s7; -|-|LOG(`"Any is now String: `" << x.Get());&] [s7; -|&] [s7; -|if(x.Is())&] [s7; -|-|LOG(`"Any is now Color: `" << x.Get());&] [s7; `}&] [s0; &] [s17; Any is now Color: Color(0, 0, 128)&] [s17; Any is now String: Hello!&] [s0; &] [s3;H4;:Section`_3`_13: 3.13 [C@5 InVector], [C@5 InArray]&] [s5; [*C@5 InVector] and [*C@5 InArray] are container types quite similar to [*C@5 Vector]/``Array``, but they trade the speed of [*C@5 operator`[`]] with the ability to insert or remove elements at any position quickly. You can expect [*C@5 operator`[`]] to be about 10 times slower than in Vector (but that is still quite fast), while [*C@5 Insert] at any position scales well up to hundreds of megabytes of data (e.g. [*C@5 InVector] containing 100M of String elements is handled without problems).&] [s0; &] [s7; InVector v;&] [s7; for(int i `= 0; i < 1000000; i`+`+)&] [s7; -|v.Add(i);&] [s7; v.Insert(0, `-1); // This is fast&] [s0; &] [s5; While the interface of [*C@5 InVector]/``InArray`` is almost identical to [*C@5 Vector]/``Array``, [*C@5 InVector]/``InArray`` in addition implements [*C@5 FindLowerBound]/``FindUpperBound`` methods `- while normal generic range algorithms work, it is possible to provide [*C@5 InVector]/``InArray`` specific optimizations that basically match the performace of [*C@5 Find`*Bound] on simple [*C@5 Vector].&] [s0; &] [s7; DUMP(v.FindLowerBound(55));&] [s0; &] [s17; v.FindLowerBound(55) `= 56&] [s0; &] [s3;H4;:Section`_3`_14: 3.14 [C@5 SortedIndex], [C@5 SortedVectorMap], [C@5 SortedArrayMap]&] [s5; [*C@5 SortedIndex] is similar to regular [*C@5 Index], but keeps its elements in sorted order (sorting predicate is a template parameter, defaults to [*C@5 StdLess]). Implementation is using [*C@5 InVector], so it works fine even with very large number of elements (performance is similar to tree based [*C@5 std`::set]). Unlike [*C@5 Index], [*C@5 SortedIndex] provides lower/upper bounds searches, so it allows range search.&] [s0; &] [s7; SortedIndex x;&] [s7; x.Add(5);&] [s7; x.Add(3);&] [s7; x.Add(7);&] [s7; x.Add(1);&] [s7; &] [s7; DUMPC(x);&] [s7; DUMP(x.Find(3));&] [s7; DUMP(x.Find(3));&] [s7; DUMP(x.FindLowerBound(3));&] [s7; DUMP(x.FindUpperBound(6));&] [s0; &] [s17; x:&] [s17; -|`[0`] `= 1&] [s17; -|`[1`] `= 3&] [s17; -|`[2`] `= 5&] [s17; -|`[3`] `= 7&] [s17; x.Find(3) `= 1&] [s17; x.Find(3) `= 1&] [s17; x.FindLowerBound(3) `= 1&] [s17; x.FindUpperBound(6) `= 3&] [s0; &] [s5; [*C@5 SortedVectorMap] and [*C@5 SortedArrayMap] are then [*C@5 SortedIndex] based equivalents to [*C@5 VectorMap]/``ArrayMap``:&] [s0; &] [s7; SortedVectorMap m;&] [s7; m.Add(`"zulu`", 11);&] [s7; m.Add(`"frank`", 12);&] [s7; m.Add(`"alfa`", 13);&] [s7; &] [s7; DUMPM(m);&] [s7; DUMP(m.Get(`"zulu`"));&] [s0; &] [s17; m:&] [s17; -|`[0`] `= (alfa) 13&] [s17; -|`[1`] `= (frank) 12&] [s17; -|`[2`] `= (zulu) 11&] [s17; m.Get(`"zulu`") `= 11&] [s0; &] [s3;H4;:Section`_3`_15: 3.15 Tuples&] [s5; Template class [*C@5 Tuple] allows combining 2`-4 values with different types. These are principally similar to [*C@5 std`::tuple], with some advantages. Unlike [*C@5 std`::tuple], individual elements are directly accessible as member variables [*C@5 a]..``d``, [*C@5 Tuple] supports persistent storage patterns ([*C@5 Serialize], [*C@5 Jsonize], [*C@5 Xmlize]), hash code ([*C@5 GetHashValue]), conversion to [*C@5 String] and Value conversions.&] [s5; To create a [*C@5 Tuple] value, you can use the [*C@5 MakeTuple] function.&] [s0; &] [s7; Tuple x `= MakeTuple(12, `"hello`", `"world`");&] [s0; &] [s5; Individual values are accessible as members [*C@5 a] .. [*C@5 d]:&] [s0; &] [s7; DUMP(x.a);&] [s7; DUMP(x.b);&] [s7; DUMP(x.c);&] [s0; &] [s17; x.a `= 12&] [s17; x.b `= hello&] [s17; x.c `= world&] [s0; &] [s5; Or using [*C@5 Get]:&] [s0; &] [s7; DUMP(x.Get<1>());&] [s7; DUMP(x.Get());&] [s0; &] [s17; x.Get<1>() `= hello&] [s17; x.Get() `= 12&] [s0; &] [s5; As long as all individual types have conversion to [*C@5 String] ([*C@5 AsString]), the tuple also has such conversion and thus can e.g. be easily logged:&] [s0; &] [s7; DUMP(x);&] [s0; &] [s17; x `= (12, hello, world)&] [s0; &] [s5; As long as individual types have defined [*C@5 GetHashValue], so does [*C@5 Tuple]:&] [s0; &] [s7; DUMP(GetHashValue(x));&] [s0; &] [s17; GetHashValue(x) `= 2465159845&] [s0; &] [s5; As long as individual types have defined [*C@5 operator`=`=], [*C@5 Tuple] has defined [*C@5 operator`=`=] and [*C@5 operator!`=]:&] [s0; &] [s7; Tuple y `= x;&] [s7; DUMP(x `=`= y);&] [s7; DUMP(x !`= y);&] [s7; y.a`+`+;&] [s7; DUMP(x `=`= y);&] [s7; DUMP(x !`= y);&] [s0; &] [s17; x `=`= y `= true&] [s17; x !`= y `= false&] [s17; x `=`= y `= false&] [s17; x !`= y `= true&] [s0; &] [s5; As long as all individual types have defined [*C@5 SgnCompare], Tuple has SgnCompare, Compare method and operators <, <`=, >, >`=:&] [s0; &] [s7; DUMP(x.Compare(y));&] [s7; DUMP(SgnCompare(x, y));&] [s7; DUMP(x < y);&] [s0; &] [s17; x.Compare(y) `= `-1&] [s17; SgnCompare(x, y) `= `-1&] [s17; x < y `= true&] [s0; &] [s5; GetCount returns the width of [*C@5 Tuple]:&] [s0; &] [s7; DUMP(x.GetCount());&] [s0; &] [s17; x.GetCount() `= 3&] [s0; &] [s5; Elements that are directly convertible with [*C@5 Value] can be `'Get`'/`'Set`':&] [s0; &] [s7; for(int i `= 0; i < x.GetCount(); i`+`+)&] [s7; -|DUMP(x.Get(i));&] [s0; &] [s17; x.Get(i) `= 12&] [s17; x.Get(i) `= hello&] [s17; x.Get(i) `= world&] [s0; &] [s0; &] [s7; x.Set(1, `"Hi`");&] [s7; DUMP(x);&] [s0; &] [s17; x `= (12, Hi, world)&] [s0; &] [s5; As long as all individual types are convertible with [*C@5 Value], you can convert Tuple to [*C@5 ValueArray] and back:&] [s0; &] [s7; ValueArray va `= x.GetArray();&] [s7; DUMP(va);&] [s7; &] [s7; va.Set(2, `"Joe`");&] [s7; x.SetArray(va);&] [s0; &] [s17; va `= `[12, Hi, world`]&] [s0; &] [s5; It is OK to assign [*C@5 Tuple] to [*C@5 Tuple] with different individual types, as long as types are directly convertible:&] [s0; &] [s7; Tuple d `= x;&] [s7; DUMP(d);&] [s0; &] [s17; d `= (12, Hi, Joe)&] [s0; &] [s5; Tie can be used to assign tuple to l`-values:&] [s0; &] [s7; int i;&] [s7; String s1, s2;&] [s7; &] [s7; Tie(i, s1, s2) `= x;&] [s7; &] [s7; DUMP(i);&] [s7; DUMP(s1);&] [s7; DUMP(s2);&] [s0; &] [s17; i `= 12&] [s17; s1 `= Hi&] [s17; s2 `= Joe&] [s0; &] [s5; U`+`+ Tuples are carefully designed as POD type, which allows POD arrays to be intialized with classic C style:&] [s0; &] [s7; static Tuple2 map`[`] `= `{&] [s7; -|`{ 1, `"one`" `},&] [s7; -|`{ 2, `"one`" `},&] [s7; -|`{ 3, `"one`" `},&] [s7; `};&] [s0; &] [s5; Simple FindTuple template function is provided to search for tuple based on the first value ([*C@5 a]) (linear O(n) search):&] [s0; &] [s7; DUMP(FindTuple(map, `_`_countof(map), 3)`->b);&] [s0; &] [s17; FindTuple(map, `_`_countof(map), 3)`->b `= one&] [s0; &] [s22;:Chapter`_4: 4. Ranges and algorithms&] [s3;:Section`_4`_1: 4.1 Range&] [s5; Unlike STL, which interface algorithms with data using [*C@5 begin] / [*C@5 end] pair, U`+`+ algorithms usually work on [/ Ranges]. Range is an object that has [*C@5 begin] / [*C@5 end] methods providing random access to elements (all U`+`+ containers are random access), [*C@5 operator`[`]] and [*C@5 GetCount] method.&] [s5; Obviously, U`+`+ containers are ranges:&] [s0; &] [s7; Vector x `= `{ 1, 2, 3, 4, 5, 1, 2, 3, 4 `};&] [s7; &] [s7; DUMP(FindIndex(x, 2)); // FindIndex is a trivial algorithm that does linear search&] [s0; &] [s17; FindIndex(x, 2) `= 1&] [s0; &] [s5; If you want the algorithm to run on part of container only, you can use [*C@5 SubRange] instance:&] [s0; &] [s7; DUMP(SubRange(x, 3, 6));&] [s7; DUMP(FindIndex(SubRange(x, 3, 6), 4));&] [s0; &] [s17; SubRange(x, 3, 6) `= `[4, 5, 1, 2, 3, 4`]&] [s17; FindIndex(SubRange(x, 3, 6), 4) `= 0&] [s0; &] [s5; As a side`-job, SubRange can also be created from `'begin`' / `'end`' pair, thus e.g. allowing algorithms to work on C arrays:&] [s0; &] [s7; int a`[`] `= `{ 1, 22, 4, 2, 8 `};&] [s7; &] [s7; auto ar `= SubRange(std`::begin(a), std`::end(a));&] [s7; &] [s7; DUMP(ar);&] [s0; &] [s17; ar `= `[1, 22, 4, 2, 8`]&] [s0; &] [s0; &] [s7; Sort(ar);&] [s7; DUMP(ar);&] [s0; &] [s17; ar `= `[1, 2, 4, 8, 22`]&] [s0; &] [s5; There are some macro aliases that make type management of ranges easier:&] [s0; &] [s7; DUMP(typeid(ValueTypeOf).name());&] [s7; DUMP(typeid(ValueTypeOf).name());&] [s7; DUMP(typeid(IteratorOf).name());&] [s7; DUMP(typeid(ConstIteratorOf).name());&] [s7; DUMP(typeid(SubRangeOf>).name());&] [s0; &] [s17; typeid(ValueTypeOf).name() `= i&] [s17; typeid(ValueTypeOf).name() `= i&] [s17; typeid(IteratorOf).name() `= Pi&] [s17; typeid(ConstIteratorOf).name() `= Pi&] [s17; typeid(SubRangeOf>).name() `= N3Upp13SubRangeClassIPiEE&] [s0; &] [s5; While containers themselves and SubRange are the two most common range types, U`+`+ has two special ranges. [*C@5 ConstRange] simply provides the range of single value:&] [s0; &] [s7; DUMP(ConstRange(1, 10));&] [s0; &] [s17; ConstRange(1, 10) `= `[1, 1, 1, 1, 1, 1, 1, 1, 1, 1`]&] [s0; &] [s5; [*C@5 ReverseRange] reverses the order of elements in the source range:&] [s0; &] [s7; Vector v`{ 1, 2, 3, 4 `};&] [s7; &] [s7; DUMP(ReverseRange(v));&] [s0; &] [s17; ReverseRange(v) `= `[4, 3, 2, 1`]&] [s0; &] [s5; [*C@5 ViewRange] picks a source range and [*C@5 Vector] of integer indices a provides a view of source range through this [*C@5 Vector]:&] [s0; &] [s7; Vector h`{ 2, 4, 0 `};&] [s7; &] [s7; DUMP(ViewRange(x, clone(h)));&] [s0; &] [s17; ViewRange(x, clone(h)) `= `[3, 5, 1`]&] [s0; &] [s0; &] [s7; Sort(ViewRange(x, clone(h)));&] [s7; DUMP(ViewRange(x, clone(h)));&] [s7; DUMP(x);&] [s0; &] [s17; ViewRange(x, clone(h)) `= `[1, 3, 5`]&] [s17; x `= `[5, 2, 1, 4, 3, 1, 2, 3, 4`]&] [s0; &] [s5; [*C@5 SortedRange] returns range sorted by predicate (default is std`::less):&] [s0; &] [s7; DUMP(SortedRange(x));&] [s0; &] [s17; SortedRange(x) `= `[1, 1, 2, 2, 3, 3, 4, 4, 5`]&] [s0; &] [s5; Finally [*C@5 FilterRange] creates a subrange of elements satisfying certain condition:&] [s0; &] [s7; DUMP(FilterRange(x, `[`](int x) `{ return x > 3; `}));&] [s0; &] [s17; FilterRange(x, `[`](int x) `{ return x > 3; `}) `= `[5, 4, 4`]&] [s0; &] [s5; Various Range functions can be combined to produce complex results:&] [s0; &] [s7; DUMP(ReverseRange(FilterRange(x, `[`](int x) `{ return x < 4; `})));&] [s0; &] [s17; ReverseRange(FilterRange(x, `[`](int x) `{ return x < 4; `})) `= `[3, 2, 1, 3, 1, 2`]&] [s0; &] [s3;H4;:Section`_4`_2: 4.2 Algorithms&] [s5; In principle, is is possible to apply C`+`+ standard library algorithms on U`+`+ containers or ranges.&] [s5; U`+`+ algorithms are tuned for U`+`+ approach `- they work on ranges and they prefer indices. Sometimes, U`+`+ algorithm will perform faster with U`+`+ types than standard library algorithm.&] [s5; [*C@5 FindIndex] performs linear search to find element with given value and returns its index or `-1 if not found:&] [s0; &] [s7; Vector data `{ 5, 3, 7, 9, 3, 4, 2 `};&] [s7; &] [s7; &] [s7; DUMP(FindIndex(data, 3));&] [s7; DUMP(FindIndex(data, 6));&] [s0; &] [s17; FindIndex(data, 3) `= 1&] [s17; FindIndex(data, 6) `= `-1&] [s0; &] [s5; [*C@5 SubRange] can be used to apply algorithm on subrange of container:&] [s0; &] [s7; DUMP(FindIndex(SubRange(data, 2, data.GetCount() `- 2), 3));&] [s0; &] [s17; FindIndex(SubRange(data, 2, data.GetCount() `- 2), 3) `= 2&] [s0; &] [s5; [*C@5 FindMin] and [*C@5 FindMax] return the index of minimal / maximal element:&] [s0; &] [s7; DUMP(FindMin(data));&] [s7; DUMP(FindMax(data));&] [s0; &] [s17; FindMin(data) `= 6&] [s17; FindMax(data) `= 3&] [s0; &] [s5; [*C@5 Min] and [*C@5 Max] return the [/ value] of minimal / maximal element:&] [s0; &] [s7; DUMP(Min(data));&] [s7; DUMP(Max(data));&] [s0; &] [s17; Min(data) `= 2&] [s17; Max(data) `= 9&] [s0; &] [s5; If the range is empty, [*C@5 Min] and [*C@5 Max] are undefined (ASSERT fails in debug mode), unless the value is specified as second parameter to be used in this case:&] [s0; &] [s7; -|Vector empty;&] [s7; //-|DUMP(Min(empty)); // This is undefined (fails in ASSERT)&] [s7; -|DUMP(Min(empty, `-99999));&] [s0; &] [s17; Min(empty, `-99999) `= `-99999&] [s0; &] [s5; [*C@5 Count] returns the number of elements with specified value, [*C@5 CountIf] the number of elements that satisfy predicate:&] [s0; &] [s7; DUMP(Count(data, 11));&] [s7; DUMP(CountIf(data, `[`=`](int c) `{ return c >`= 5; `}));&] [s0; &] [s17; Count(data, 11) `= 0&] [s17; CountIf(data, `[`=`](int c) `{ return c >`= 5; `}) `= 3&] [s0; &] [s5; [*C@5 Sum] return the sum of all elements in range:&] [s0; &] [s7; DUMP(Sum(data));&] [s0; &] [s17; Sum(data) `= 33&] [s0; &] [s5; Sorted containers can be searched with bisection. U`+`+ provides usual upper / lower bound algorithms. [*C@5 FindBinary] returns the index of element with given value or `-1 if not found:&] [s0; &] [s7; data `= `{ 5, 7, 9, 9, 14, 20, 23, 50 `};&] [s7; // 0 1 2 3 4 5 6 7&] [s7; DUMP(FindLowerBound(data, 9));&] [s7; DUMP(FindUpperBound(data, 9));&] [s7; DUMP(FindBinary(data, 9));&] [s7; DUMP(FindLowerBound(data, 10));&] [s7; DUMP(FindUpperBound(data, 10));&] [s7; DUMP(FindBinary(data, 10));&] [s0; &] [s17; FindLowerBound(data, 9) `= 2&] [s17; FindUpperBound(data, 9) `= 4&] [s17; FindBinary(data, 9) `= 2&] [s17; FindLowerBound(data, 10) `= 4&] [s17; FindUpperBound(data, 10) `= 4&] [s17; FindBinary(data, 10) `= `-1&] [s0; &] [s3;H4;:Section`_4`_3: 4.3 Sorting&] [s5; Unsurprisingly, [*C@5 Sort] function sorts a range. You can specify sorting predicate, default is [*C@5 operator<]:&] [s0; &] [s7; Vector x `{ `"1`", `"2`", `"10`" `};&] [s7; &] [s7; Sort(x);&] [s7; &] [s7; DUMP(x);&] [s0; &] [s17; x `= `[1, 10, 2`]&] [s0; &] [s0; &] [s7; Sort(x, `[`](const String`& a, const String`& b) `{ return atoi(a) < atoi(b); `});&] [s7; &] [s7; DUMP(x);&] [s0; &] [s17; x `= `[1, 2, 10`]&] [s0; &] [s5; [*C@5 IndexSort] is sort variant that is able to sort two ranges (like [*C@5 Vector] or [*C@5 Array]) of the same size, based on values in the first range:&] [s0; &] [s7; Vector a `{ 5, 10, 2, 9, 7, 3 `};&] [s7; Vector b `{ `"five`", `"ten`", `"two`", `"nine`", `"seven`", `"three`" `};&] [s7; &] [s7; IndexSort(a, b);&] [s7; &] [s7; DUMP(a);&] [s7; DUMP(b);&] [s0; &] [s17; a `= `[2, 3, 5, 7, 9, 10`]&] [s17; b `= `[two, three, five, seven, nine, ten`]&] [s0; &] [s0; &] [s7; IndexSort(b, a);&] [s7; &] [s7; DUMP(a);&] [s7; DUMP(b);&] [s0; &] [s17; a `= `[5, 9, 7, 10, 3, 2`]&] [s17; b `= `[five, nine, seven, ten, three, two`]&] [s0; &] [s5; There are also [*C@5 IndexSort2] and [*C@5 IndexSort3] variants that sort 2 or 3 dependent ranges.&] [s5; Sometimes, instead of sorting items in the range, it is useful to know the order of items as sorted, using [*C@5 GetSortOrder]:&] [s0; &] [s7; Vector o `= GetSortOrder(a);&] [s7; &] [s7; DUMP(o);&] [s0; &] [s17; o `= `[5, 4, 0, 2, 1, 3`]&] [s0; &] [s5; Normal [*C@5 Sort] is not stable `- equal items can appear in sorted range in random order. If maintaining original order of equal items is important, use [*C@5 StableSort] variant (with performance penalty):&] [s0; &] [s7; Vector t `{ Point(10, 10), Point(7, 1), Point(7, 2), Point(7, 3), Point(1, 0) `};&] [s7; StableSort(t, `[`](const Point`& a, const Point`& b) `{ return a.x < b.x; `});&] [s7; &] [s7; DUMP(t);&] [s0; &] [s17; t `= `[`[1, 0`], `[7, 1`], `[7, 2`], `[7, 3`], `[10, 10`]`]&] [s0; &] [s5; All sorting algorithms have they `'Stable`' variant, so there is [*C@5 StableIndexSort], [*C@5 GetStableSortOrder] etc...&] [s22;:Chapter`_5: 5. Value&] [s3;:Section`_5`_1: 5.1 Value&] [s5; Value is sort of equivalent of polymorphic data types from scripting languages like Python or JavaSript. [*C@5 Value] can represent values of concrete types, some types also have extended interoperability with [*C@5 Value] and it is then possible to e.g. compare [*C@5 Value]s containing such types against each other or serialize them for persistent storage.&] [s5; Usually, Value compatible types define typecast operator to [*C@5 Value] and constructor from [*C@5 Value], so that interaction is for the most part seamless:&] [s0; &] [s7; Value a `= 1;&] [s7; Value b `= 2.34;&] [s7; Value c `= GetSysDate();&] [s7; Value d `= `"hello`";&] [s7; &] [s7; DUMP(a);&] [s7; DUMP(b);&] [s7; DUMP(c);&] [s7; DUMP(d);&] [s7; &] [s7; int x `= a;&] [s7; double y `= b;&] [s7; Date z `= c;&] [s7; String s `= d;&] [s7; &] [s7; DUMP(x);&] [s7; DUMP(y);&] [s7; DUMP(z);&] [s7; DUMP(s);&] [s0; &] [s17; a `= 1&] [s17; b `= 2.34&] [s17; c `= 07/21/2021&] [s17; d `= hello&] [s17; x `= 1&] [s17; y `= 2.34&] [s17; z `= 07/21/2021&] [s17; s `= hello&] [s0; &] [s5; As for primitive types, Value seamlessly works with [*C@5 int], [*C@5 int64], [*C@5 bool] and [*C@5 double]. Casting [*C@5 Value] to a type that it does not contain throws an exception:&] [s0; &] [s7; try `{&] [s7; -|s `= a;&] [s7; -|DUMP(s); // we never get here....&] [s7; `}&] [s7; catch(ValueTypeError) `{&] [s7; -|LOG(`"Failed Value conversion`");&] [s7; `}&] [s0; &] [s17; Failed Value conversion&] [s0; &] [s5; However, conversion between related types is possible (as long as it is supported by these types):&] [s0; &] [s7; double i `= a;&] [s7; int j `= b;&] [s7; Time k `= c;&] [s7; WString t `= d;&] [s7; &] [s7; DUMP(i);&] [s7; DUMP(j);&] [s7; DUMP(k);&] [s7; DUMP(t);&] [s0; &] [s17; i `= 1&] [s17; j `= 2&] [s17; k `= 07/21/2021 00:00:00&] [s17; t `= hello&] [s0; &] [s5; To determine type of value stored in [*C@5 Value], you can use [*C@5 Is] method:&] [s0; &] [s7; DUMP(a.Is());&] [s7; DUMP(a.Is());&] [s7; DUMP(b.Is());&] [s7; DUMP(c.Is());&] [s7; DUMP(c.Is());&] [s7; DUMP(d.Is());&] [s0; &] [s17; a.Is() `= true&] [s17; a.Is() `= false&] [s17; b.Is() `= true&] [s17; c.Is() `= false&] [s17; c.Is() `= true&] [s17; d.Is() `= true&] [s0; &] [s5; Note that Is tests for absolute type match, not for compatible types. For that reason, for widely used compatible types helper functions are defined:&] [s0; &] [s7; DUMP(IsNumber(a));&] [s7; DUMP(IsNumber(b));&] [s7; DUMP(IsDateTime(c));&] [s7; DUMP(IsString(d));&] [s0; &] [s17; IsNumber(a) `= true&] [s17; IsNumber(b) `= true&] [s17; IsDateTime(c) `= true&] [s17; IsString(d) `= true&] [s0; &] [s3;H4;:Section`_5`_2: 5.2 [C@5 Null]&] [s5; U`+`+ defines a special [*C@5 Null] constant to represent an empty value. This constant is convertible to many value types including primitive types [*C@5 double], [*C@5 int] and [*C@5 int64] (defined as lowest number the type can represent). If type supports ordering (<, >), all values of the type are greater than Null value. To test whether a value is empty, use [*C@5 IsNull] function.&] [s0; &] [s7; int x `= Null;&] [s7; int y `= 120;&] [s7; Date d `= Null;&] [s7; Date e `= GetSysDate();&] [s7; &] [s7; DUMP(x);&] [s7; DUMP(y);&] [s7; DUMP(d);&] [s7; DUMP(e > d);&] [s0; &] [s17; x `= &] [s17; y `= 120&] [s17; d `= &] [s17; e > d `= true&] [s0; &] [s5; [*C@5 Null] is the only instance of [*C@5 Nuller] type. Assigning [*C@5 Null] to primitive types is achieved by cast operators of [*C@5 Nuller], other types can do it using constructor from [*C@5 Nuller].&] [s5; As a special case, if [*C@5 Value] contains [*C@5 Null], it is convertible to any value type that can contain [*C@5 Null]:&] [s0; &] [s7; Value v `= x; // x is int&] [s7; e `= v; // e is Date, but v is Null, so Null is assigned to e&] [s7; &] [s7; DUMP(IsNull(e));&] [s0; &] [s17; IsNull(e) `= true&] [s0; &] [s5; Function [*C@5 Nvl] is U`+`+ analog of well known SQL function coalesce (ifnull, Nvl), which returns the first non`-null argument (or [*C@5 Null] if all are [*C@5 Null]).&] [s0; &] [s7; int a `= Null;&] [s7; int b `= 123;&] [s7; int c `= 1;&] [s7; &] [s7; DUMP(Nvl(a, b, c));&] [s0; &] [s17; Nvl(a, b, c) `= 123&] [s0; &] [s3;H4;:Section`_5`_3: 5.3 Client types and [C@5 Value], [C@5 RawValue], [C@5 RichValue]&] [s5; There are two Value compatibility levels. The simple one, [*C@5 RawValue], has little requirements for the type used `- only copy constructor and assignment operator are required (and there are even forms of [*C@5 RawValue] that work for types missing these):&] [s0; &] [s7; struct RawFoo `{&] [s7; -|String x;&] [s7; -|// default copy constructor and assignment operator are provided by compiler&] [s7; `};&] [s0; &] [s5; To convert such type to [*C@5 Value], use [*C@5 RawToValue]:&] [s0; &] [s7; RawFoo h;&] [s7; h.x `= `"hello`";&] [s7; Value q `= RawToValue(h);&] [s7; &] [s7; DUMP(q.Is());&] [s0; &] [s17; q.Is() `= true&] [s0; &] [s5; To convert it back, us `'To`' templated member function of [*C@5 Value], it returns a constant reference to the value:&] [s0; &] [s7; DUMP(q.To().x);&] [s0; &] [s17; q.To().x `= hello&] [s0; &] [s5; [*C@5 RichValue] level [*C@5 Value]s provide more operations for [*C@5 Value] `- equality test, [*C@5 IsNull] test, hashing, conversion to text, serialization (possibly to XML and Json), comparison. In order to make serialization work, type must also have assigned an integer id (client types should use ids in range 10000..20000). Type can provide the support for these operations via template function specializations or (perhaps more convenient) using defined methods and inheriting from [*C@5 ValueType] base class template:&] [s0; &] [s7; struct Foo : ValueType `{&] [s7; -|int x;&] [s7; -|&] [s7; -|Foo(const Nuller`&) `{ x `= Null; `}&] [s7; -|Foo(int x) : x(x) `{`}&] [s7; -|Foo() `{`}&] [s7; &] [s7; -|// We provide these methods to allow automatic conversion of Foo to/from Value&] [s7; -|operator Value() const `{ return RichToValue(`*this); `}&] [s7; -|Foo(const Value`& v) `{ `*this `= v.Get(); `}&] [s7; &] [s7; -|String ToString() const `{ return AsString(x); `}&] [s7; -|unsigned GetHashValue() const `{ return x; `}&] [s7; -|void Serialize(Stream`& s) `{ s % x; `}&] [s7; -|bool operator`=`=(const Foo`& b) const `{ return x `=`= b.x; `}&] [s7; -|bool IsNullInstance() const `{ return IsNull(x); `}&] [s7; -|int Compare(const Foo`& b) const `{ return SgnCompare(x, b.x); `}&] [s7; -|// This type does not define XML nor Json serialization&] [s7; `};&] [s7; &] [s7; INITBLOCK `{ // This has to be at file level scope&] [s7; -|Value`::Register(); // need to register value type integer id to allow serialization&] [s7; `}&] [s7; &] [s7; Value a `= Foo(54321); // uses Foo`::operator Value&] [s7; Value b `= Foo(54321);&] [s7; Value c `= Foo(600);&] [s7; &] [s7; DUMP(a); // uses Foo`::ToString&] [s7; DUMP(a `=`= b); // uses Foo`::operator`=`=&] [s7; DUMP(a `=`= c);&] [s7; DUMP(c < a); // uses Foo`::Compare&] [s7; &] [s7; DUMP(IsNull(a)); // uses Foo`::IsNullInstance&] [s7; &] [s7; Foo foo `= c; // Uses Foo`::Foo(const Value`&)&] [s7; DUMP(foo);&] [s0; &] [s17; a `= 54321&] [s17; a `=`= b `= true&] [s17; a `=`= c `= false&] [s17; c < a `= true&] [s17; IsNull(a) `= false&] [s17; foo `= 600&] [s0; &] [s0; &] [s7; String s `= StoreAsString(a); // Uses Foo`::Serialize&] [s7; &] [s7; Value loaded;&] [s7; // Using registered (Value`::Registered) integer id creates the correct type, then uses&] [s7; // Foo`::Serialize to load the data from the stream&] [s7; LoadFromString(loaded, s);&] [s7; &] [s7; DUMP(loaded);&] [s0; &] [s17; loaded `= 54321&] [s0; &] [s3;H4;:Section`_5`_4: 5.4 [C@5 ValueArray] and [C@5 ValueMap]&] [s5; [*C@5 ValueArray] is a type that represents an array of [*C@5 Value]s:&] [s0; &] [s7; ValueArray va`{1, 2, 3`};&] [s7; &] [s7; DUMP(va);&] [s0; &] [s17; va `= `[1, 2, 3`]&] [s0; &] [s5; ValueArray can be assigned to Value (and back):&] [s0; &] [s7; Value v `= va;&] [s7; &] [s7; DUMP(v);&] [s7; DUMP(v.Is()); // must be exactly ValueArray&] [s7; DUMP(IsValueArray(v)); // is ValueArray or ValueMap (which is convertible to ValueArray)&] [s7; &] [s7; ValueArray va2 `= v;&] [s7; &] [s7; DUMP(va2);&] [s0; &] [s17; v `= `[1, 2, 3`]&] [s17; v.Is() `= true&] [s17; IsValueArray(v) `= true&] [s17; va2 `= `[1, 2, 3`]&] [s0; &] [s5; Elements can be appended using [*C@5 Add] method or [*C@5 operator<<], element at index can be changed with [*C@5 Set]:&] [s0; &] [s7; va.Add(10);&] [s7; va << 20 << 21;&] [s7; va.Set(0, 999);&] [s7; &] [s7; DUMP(va);&] [s0; &] [s17; va `= `[999, 2, 3, 10, 20, 21`]&] [s0; &] [s5; Elements can be removed:&] [s0; &] [s7; va.Remove(0, 2);&] [s7; &] [s7; DUMP(va);&] [s0; &] [s17; va `= `[3, 10, 20, 21`]&] [s0; &] [s5; and inserted:&] [s0; &] [s7; va.Insert(1, v);&] [s7; &] [s7; DUMP(va);&] [s0; &] [s17; va `= `[3, 1, 2, 3, 10, 20, 21`]&] [s0; &] [s5; It is possible to get a reference to element at index, however note that some [^topic`:`/`/Core`/srcdoc`/ValueReference`$en`-us^ special rules] apply here:&] [s0; &] [s7; va.At(0) `= 222;&] [s7; &] [s7; DUMP(va);&] [s0; &] [s17; va `= `[222, 1, 2, 3, 10, 20, 21`]&] [s0; &] [s5; If [*C@5 Value] contains [*C@5 ValueArray], [*C@5 Value`::GetCount] method returns the number of elements in the array (if there is no [*C@5 ValueArray] in [*C@5 Value], it returns zero). You can use [*C@5 Value`::operator`[`](int)] to get constant reference to [*C@5 ValueArray] elements:&] [s0; &] [s7; for(int i `= 0; i < v.GetCount(); i`+`+)&] [s7; -|LOG(v`[i`]);&] [s0; &] [s17; 1&] [s17; 2&] [s17; 3&] [s0; &] [s5; It is even possible to directly add element to [*C@5 Value] if it contains [*C@5 ValueArray]:&] [s0; &] [s7; v.Add(4);&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[1, 2, 3, 4`]&] [s0; &] [s5; Or even get a reference to element at some index (with [^topic`:`/`/Core`/srcdoc`/ValueReference`$en`-us^ s pecial rules]):&] [s0; &] [s7; v.At(0) `= 111;&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `[111, 2, 3, 4`]&] [s0; &] [s5; [*C@5 ValueMap] can store key `- value pairs and retrieve value for key quickly. Note that keys are not limited to [*C@5 String], but can be any [*C@5 Value] with [*C@5 operator`=`=] and hash code defined.&] [s5; [*C@5 Add] method or [*C@5 operator()] add data to [*C@5 ValueMap]:&] [s0; &] [s7; ValueMap m;&] [s7; &] [s7; m.Add(`"one`", 1);&] [s7; m(`"two`", 2)(`"three`", 3);&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{ one: 1, two: 2, three: 3 `}&] [s0; &] [s5; [*C@5 operator`[`]] retrieves the value at the key:&] [s0; &] [s7; DUMP(m`[`"two`"`]);&] [s0; &] [s17; m`[`"two`"`] `= 2&] [s0; &] [s5; When key is not present in the map, [*C@5 operator`[`]] returns void Value (which is also Null):&] [s0; &] [s7; DUMP(m`[`"key`"`]);&] [s7; DUMP(m`[`"key`"`].IsVoid());&] [s7; DUMP(IsNull(m`[`"key`"`]));&] [s0; &] [s17; m`[`"key`"`] `= &] [s17; m`[`"key`"`].IsVoid() `= true&] [s17; IsNull(m`[`"key`"`]) `= true&] [s0; &] [s5; Just like [*C@5 VectorMap], [*C@5 ValueMap] is ordered, so the order of adding pairs to it matters:&] [s0; &] [s7; ValueMap m2;&] [s7; &] [s7; m2.Add(`"two`", 2);&] [s7; m2(`"one`", 1)(`"three`", 3);&] [s7; &] [s7; DUMP(m2);&] [s7; DUMP(m `=`= m2); // different order of adding means they are not equal&] [s0; &] [s17; m2 `= `{ two: 2, one: 1, three: 3 `}&] [s17; m `=`= m2 `= false&] [s0; &] [s5; `'Unordered`' equality test can be done using [*C@5 IsSame]:&] [s0; &] [s7; DUMP(m.IsSame(m2));&] [s0; &] [s17; m.IsSame(m2) `= true&] [s0; &] [s5; Iterating ValueMap can be achieved with [*C@5 GetCount], [*C@5 GetKey] and [*C@5 GetValue]:&] [s0; &] [s7; for(int i `= 0; i < m.GetCount(); i`+`+)&] [s7; -|LOG(m.GetKey(i) << `" `= `" << m.GetValue(i));&] [s0; &] [s17; one `= 1&] [s17; two `= 2&] [s17; three `= 3&] [s0; &] [s5; It is possible to get [*C@5 ValueArray] of values:&] [s0; &] [s7; LOG(m.GetValues());&] [s0; &] [s17; `[1, 2, 3`]&] [s0; &] [s5; [*C@5 GetKeys] gets constant reference to [*C@5 Index] of keys:&] [s0; &] [s7; LOG(m.GetKeys());&] [s0; &] [s17; `[one, two, three`]&] [s0; &] [s5; It is possible to change the value with [*C@5 Set]:&] [s0; &] [s7; m.Set(`"two`", 4);&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{ one: 1, two: 4, three: 3 `}&] [s0; &] [s5; Or to change the value of key with [*C@5 SetKey]:&] [s0; &] [s7; m.SetKey(1, `"four`");&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{ one: 1, four: 4, three: 3 `}&] [s0; &] [s5; It is possible get a reference of value at given key, (with [^topic`:`/`/Core`/srcdoc`/ValueReference`$en`-us^ special rules]) with [*C@5 GetAdd] or [*C@5 operator()]:&] [s0; &] [s7; Value`& h `= m(`"five`");&] [s7; &] [s7; h `= 5;&] [s7; &] [s7; DUMP(m);&] [s0; &] [s17; m `= `{ one: 1, four: 4, three: 3, five: 5 `}&] [s0; &] [s5; When ValueMap is stored into Value, [*C@5 operator`[`](String)] provides access to value at key. Note that this narrows keys to text values:&] [s0; &] [s7; v `= m;&] [s7; DUMP(v);&] [s7; DUMP(v`[`"five`"`]);&] [s0; &] [s17; v `= `{ one: 1, four: 4, three: 3, five: 5 `}&] [s17; v`[`"five`"`] `= 5&] [s0; &] [s5; [*C@5 Value`::GetAdd] and [*C@5 Value`::operator()] provide a reference to value at key, with [^topic`:`/`/Core`/srcdoc`/ValueReference`$en`-us^ special rules]:&] [s0; &] [s7; v.GetAdd(`"newkey`") `= `"foo`";&] [s7; v(`"five`") `= `"FIVE`";&] [s7; &] [s7; DUMP(v);&] [s0; &] [s17; v `= `{ one: 1, four: 4, three: 3, five: FIVE, newkey: foo `}&] [s0; &] [s5; [*C@5 ValueMap] and [*C@5 ValueArray] are convertible with each other. When assigning [*C@5 ValueMap] to [*C@5 ValueArray], values are simply used:&] [s0; &] [s7; ValueArray v2 `= m;&] [s7; &] [s7; DUMP(v2);&] [s0; &] [s17; v2 `= `[1, 4, 3, 5`]&] [s0; &] [s5; When assigning [*C@5 ValueArray] to [*C@5 ValueMap], keys are set as indices of elements:&] [s0; &] [s7; ValueMap m3 `= v2;&] [s7; &] [s7; DUMP(m3);&] [s0; &] [s17; m3 `= `{ 0: 1, 1: 4, 2: 3, 3: 5 `}&] [s0; &] [s5; With basic [*C@5 Value] types [*C@5 int], [*C@5 String], [*C@5 ValueArray] and [*C@5 ValueMap], [*C@5 Value] can represent JSON:&] [s0; &] [s7; Value j `= ParseJSON(`"`{ `\`"array`\`" : `[ 1, 2, 3 `] `}`");&] [s7; &] [s7; DUMP(j);&] [s0; &] [s17; j `= `{ array: `[1, 2, 3`] `}&] [s0; &] [s0; &] [s7; j(`"value`") `= m;&] [s7; &] [s7; DUMP(AsJSON(j));&] [s0; &] [s17; AsJSON(j) `= `{`"array`":`[1,2,3`],`"value`":`{`"one`":1,`"four`":4,`"three`":3,`"fiv e`":5`}`}&] [s0; &] [s0; &] [s7; j(`"array`").At(1) `= ValueMap()(`"key`", 1);&] [s7; &] [s7; DUMP(AsJSON(j));&] [s0; &] [s17; AsJSON(j) `= `{`"array`":`[1,`{`"key`":1`},3`],`"value`":`{`"one`":1,`"four`":4,`"thr ee`":3,`"five`":5`}`}&] [s0; &] [s22;:Chapter`_6: 6. Function and lambdas&] [s3;:Section`_6`_1: 6.1 Function&] [s5; U`+`+ [*C@5 Function] is quite similar to [*C@5 std`::function] `- it is a function wrapper that can store/copy/invoke any callable target. There are two important differences. First, invoking empty [*C@5 Function] is NOP, if [*C@5 Function] has return type [*C@5 T], it returns [*C@5 T()]. Second, [*C@5 Function] allows effective chaining of callable targets using [*C@5 operator<<], if [*C@5 Function] has return type, the return type of last callable appended is used.&] [s5; Usually, the callable target is C`+`+11 lambda:&] [s0; &] [s7; Function fn `= `[`](int n) `{ LOG(`"Called A`"); return 3 `* n; `};&] [s7; &] [s7; LOG(`"About to call function`");&] [s7; int n `= fn(7);&] [s7; DUMP(n);&] [s0; &] [s17; About to call function&] [s17; Called A&] [s17; n `= 21&] [s0; &] [s5; If you chain another lambda into [*C@5 Function], all are called, but the last one`'s return value is used:&] [s0; &] [s7; fn << `[`](int n) `{ LOG(`"Called B`"); return n `* n; `};&] [s7; LOG(`"About to call combined function`");&] [s7; n `= fn(7);&] [s7; DUMP(n);&] [s0; &] [s17; About to call combined function&] [s17; Called A&] [s17; Called B&] [s17; n `= 49&] [s0; &] [s5; Invoking empty lambda does nothing and returns default constructed return value. This is quite useful for GUI classes, which have a lot of output events represented by [*C@5 Function] which are often unassigned to any action.&] [s0; &] [s7; fn.Clear();&] [s7; LOG(`"About to call empty function`");&] [s7; n `= fn(7);&] [s7; DUMP(n);&] [s0; &] [s17; About to call empty function&] [s17; n `= 0&] [s0; &] [s5; While using [*C@5 Function] with lambda expression is the most common, you can use any target that has corresponding [*C@5 operator()] defined:&] [s0; &] [s7; struct Functor `{&] [s7; -|int operator()(int x) `{ LOG(`"Called Foo`"); return x % 2; `}&] [s7; `};&] [s7; &] [s7; fn `= Functor();&] [s7; LOG(`"About to call Functor`");&] [s7; n `= fn(7);&] [s7; DUMP(n);&] [s0; &] [s17; About to call Functor&] [s17; Called Foo&] [s17; n `= 1&] [s0; &] [s5; As [*C@5 Function] with [*C@5 void] and [*C@5 bool] return types are the most frequently used, U`+`+ defines template aliases [*C@5 Event]:&] [s0; &] [s7; Event<> ev `= `[`] `{ LOG(`"Event invoked`"); `};&] [s7; &] [s7; ev();&] [s0; &] [s17; Event invoked&] [s0; &] [s5; and [*C@5 Gate]:&] [s0; &] [s7; Gate gt `= `[`](int x) `{ LOG(`"Gate invoked with `" << x); return x < 10; `};&] [s7; &] [s7; bool b `= gt(9);&] [s7; DUMP(b);&] [s7; b `= gt(10);&] [s7; DUMP(b);&] [s0; &] [s17; Gate invoked with 9&] [s17; b `= true&] [s17; Gate invoked with 10&] [s17; b `= false&] [s0; &] [s5; Using lambda to define calls to methods with more parameters can be verbose and error`-prone. The issue can be simplified by using [*C@5 THISFN] macro:&] [s0; &] [s7; struct Foo `{&] [s7; -|void Test(int a, const String`& b) `{ LOG(`"Foo`::Test `" << a << `", `" << b); `}&] [s7; -|&] [s7; -|typedef Foo CLASSNAME; // required for THISFN&] [s7; -|&] [s7; -|void Do() `{&] [s7; -|-|Event fn;&] [s7; -|-|&] [s7; -|-|fn `= `[`=`](int a, const String`& b) `{ Test(a, b); `};&] [s7; -|-|fn(1, `"using lambda`");&] [s7; -|-|&] [s7; -|-|fn `= THISFN(Test); // this is functionally equivalent, but less verbose&] [s7; -|-|fn(2, `"using THISFN`");&] [s7; -|`}&] [s7; `};&] [s7; &] [s7; Foo f;&] [s7; f.Do();&] [s0; &] [s17; Foo`::Test 1, using lambda&] [s17; Foo`::Test 2, using THISFN&] [s0; &] [s3;H4;:Section`_6`_2: 6.2 Capturing U`+`+ containers into lambdas&] [s5; Capturing objects with pick/clone semantics can be achieved using [/ capture with an initializer]:&] [s0; &] [s7; Vector x`{ 1, 2 `};&] [s7; Array y`{ `"one`", `"two`" `};&] [s7; Event<> ev `= `[x `= pick(x), y `= clone(y)`] `{ DUMP(x); DUMP(y); `};&] [s7; &] [s7; DUMP(x); // x is picked, so empty&] [s7; DUMP(y); // y was cloned, so it retains original value&] [s7; &] [s7; LOG(`"About to invoke event`");&] [s7; &] [s7; ev();&] [s0; &] [s17; x `= `[`]&] [s17; y `= `[one, two`]&] [s17; About to invoke event&] [s17; x `= `[1, 2`]&] [s17; y `= `[one, two`]&] [s0; &] [s22;:Chapter`_7: 7. Multithreading&] [s3;:Section`_7`_1: 7.1 [C@5 Thread]&] [s5; Since C`+`+11, there is now a reasonable support for threads in standard library. There are however reasons to use U`+`+ threads instead. One of them is that U`+`+ high performance memory allocator needs a cleanup call at the the thread exit, which is naturally implemented into [*C@5 Upp`::Thread]. Second `'hard`' reason is that Microsoft compiler is using Win32 API function for condition variable that are not available for Windows XP, while U`+`+ has alternative implementation for Windows XP, thus making executable compatible with it.&] [s5; Then of course we believe U`+`+ multithreading / parallel programming support is easier to use and leads to higher performance...&] [s5; [*C@5 Thread] class can start the thread and allows launching thread to [*C@5 Wait] for its completion:&] [s0; &] [s7; Thread t;&] [s7; t.Run(`[`] `{&] [s7; -|for(int i `= 0; i < 10; i`+`+) `{&] [s7; -|-|LOG(`"In the thread `" << i);&] [s7; -|-|Sleep(100);&] [s7; -|`}&] [s7; -|LOG(`"Thread is ending...`");&] [s7; `});&] [s7; for(int i `= 0; i < 5; i`+`+) `{&] [s7; -|LOG(`"In the main thread `" << i);&] [s7; -|Sleep(100);&] [s7; `}&] [s7; LOG(`"About to wait for thread to finish`");&] [s7; t.Wait();&] [s7; LOG(`"Wait for thread done`");&] [s0; &] [s17; In the main thread 0&] [s17; In the thread 0&] [s17; In the thread 1&] [s17; In the main thread 1&] [s17; In the main thread 2&] [s17; In the thread 2&] [s17; In the main thread 3&] [s17; In the thread 3&] [s17; In the main thread 4&] [s17; In the thread 4&] [s17; About to wait for thread to finish&] [s17; In the thread 5&] [s17; In the thread 6&] [s17; In the thread 7&] [s17; In the thread 8&] [s17; In the thread 9&] [s17; Thread is ending...&] [s17; Wait for thread done&] [s0; &] [s5; [*C@5 Thread] destructor calls [*C@5 Detach] method with `'disconnects`' [*C@5 Thread] from the thread. Thread continues running.&] [s5; [*C@5 Thread`::Start] static method launches a thread without possibility to wait for its completion; if you need to wait, you have to use some other method:&] [s0; &] [s7; bool x `= false;&] [s7; &] [s7; Thread`::Start(`[`&x`] `{ LOG(`"In the Started thread`"); x `= true; `});&] [s7; &] [s7; LOG(`"About to wait for thread to finish`");&] [s7; while(!x) `{ Sleep(1); `} // Do not do this in real code!&] [s7; LOG(`"Wait for thread done`");&] [s0; &] [s17; About to wait for thread to finish&] [s17; In the Started thread&] [s17; Wait for thread done&] [s0; &] [s5; (method used here is horrible, but should demonstrate the point).&] [s3;H4;:Section`_7`_2: 7.2 [C@5 Mutex]&] [s5; Mutex (`"mutual exclusion`") is a well known concept in multithreaded programming: When multiple threads write and read the same data, the access has to be serialized using Mutex. Following invalid code demonstrates why:&] [s0; &] [s7; Thread t;&] [s7; &] [s7; int sum `= 0;&] [s7; t.Run(`[`&sum`] `{&] [s7; -|for(int i `= 0; i < 1000000; i`+`+)&] [s7; -|-|sum`+`+;&] [s7; `});&] [s7; &] [s7; for(int i `= 0; i < 1000000; i`+`+)&] [s7; -|sum`+`+;&] [s7; &] [s7; t.Wait();&] [s7; DUMP(sum);&] [s0; &] [s17; sum `= 1631489&] [s0; &] [s5; While the expected value is 2000000, produced value is different. The problem is that both thread read / modify / write [*C@5 sum] value without any locking. Using [*C@5 Mutex] locks the [*C@5 sum] and thus serializes access to it `- read / modify / write sequence is now exclusive for the thread that has [*C@5 Mutex] locked, this fixing the issue. [*C@5 Mutex] can be locked / unlocked with [*C@5 Enter] / [*C@5 Leave] methods. Alternatively, [*C@5 Mutex`::Lock] helper class locks [*C@5 Mutex] in constructor and unlocks it in destructor:&] [s0; &] [s7; Mutex m;&] [s7; sum `= 0;&] [s7; t.Run(`[`&sum, `&m`] `{&] [s7; -|for(int i `= 0; i < 1000000; i`+`+) `{&] [s7; -|-|m.Enter();&] [s7; -|-|sum`+`+;&] [s7; -|-|m.Leave();&] [s7; -|`}&] [s7; `});&] [s7; &] [s7; for(int i `= 0; i < 1000000; i`+`+) `{&] [s7; -|Mutex`::Lock `_`_(m); // Lock m till the end of scope&] [s7; -|sum`+`+;&] [s7; `}&] [s7; &] [s7; t.Wait();&] [s7; DUMP(sum);&] [s0; &] [s17; sum `= 2000000&] [s0; &] [s3;H4;:Section`_7`_3: 7.3 [C@5 ConditionVariable]&] [s5; [*C@5 ConditionVariable] in general is a synchronization primitive used to block/awaken the thread. [*C@5 ConditionVariable] is associated with [*C@5 Mutex] used to protect some data; in the thread that is to be blocked, [*C@5 Mutex] has to locked; call to [*C@5 Wait] atomically unlocks the [*C@5 Mutex] and puts the thread to waiting. Another thread then can resume the thread by calling [*C@5 Signal], which also causes [*C@5 Mutex] to lock again. Multiple threads can be waiting on single [*C@5 ConditionVariable]; [*C@5 Signal] resumes single waiting thread, [*C@5 Brodcast] resumes all waitng threads.&] [s0; &] [s7; bool stop `= false;&] [s7; BiVector data;&] [s7; Mutex m;&] [s7; ConditionVariable cv;&] [s7; &] [s7; Thread t;&] [s7; t.Run(`[`&stop, `&data, `&m, `&cv`] `{&] [s7; -|Mutex`::Lock `_`_(m);&] [s7; -|for(;;) `{&] [s7; -|-|while(data.GetCount()) `{&] [s7; -|-|-|int q `= data.PopTail();&] [s7; -|-|-|LOG(`"Data received: `" << q);&] [s7; -|-|`}&] [s7; -|-|if(stop)&] [s7; -|-|-|break;&] [s7; -|-|cv.Wait(m);&] [s7; -|`}&] [s7; `});&] [s7; &] [s7; for(int i `= 0; i < 10; i`+`+) `{&] [s7; -|`{&] [s7; -|-|Mutex`::Lock `_`_(m);&] [s7; -|-|data.AddHead(i);&] [s7; -|`}&] [s7; -|cv.Signal();&] [s7; -|Sleep(1);&] [s7; `}&] [s7; stop `= true;&] [s7; cv.Signal();&] [s7; t.Wait();&] [s0; &] [s17; Data received: 0&] [s17; Data received: 1&] [s17; Data received: 2&] [s17; Data received: 3&] [s17; Data received: 4&] [s17; Data received: 5&] [s17; Data received: 6&] [s17; Data received: 7&] [s17; Data received: 8&] [s17; Data received: 9&] [s0; &] [s5; Important note: rarely thread can be resumed from [*C@5 Wait] even if no other called [*C@5 Signal]. This is not a bug, but [^https`:`/`/en`.wikipedia`.org`/wiki`/Spurious`_wakeup^ d esign decision for performance reason]. In practice it only means that situation has to be (re)checked after resume.&] [s3;H4;:Section`_7`_4: 7.4 [C@5 CoWork]&] [s5; [*C@5 CoWork] is intented to be use when thread are used to speedup code by distributing tasks over multiple CPU cores. [*C@5 CoWork] spans a single set of worker threads that exist for the whole duration of program run. [*C@5 CoWork] instances then manage assigning jobs to these worker threads and waiting for the all work to finish.&] [s5; Job units to [*C@5 CoWork] are represented by [*C@5 Function] and thus can be written inline as lambdas.&] [s5; As an example, following code reads input file by lines, splits lines into words (this is the parallelized work) and then adds resulting words to [*C@5 Index]:&] [s0; &] [s7; FileIn in(GetDataFile(`"test.txt`")); // let us open some tutorial testing data&] [s7; &] [s7; Index w;&] [s7; Mutex m; // need mutex to serialize access to w&] [s7; &] [s7; CoWork co;&] [s7; while(!in.IsEof()) `{&] [s7; -|String ln `= in.GetLine();&] [s7; -|co `& `[ln, `&w, `&m`] `{&] [s7; -|-|Vector h `= Split(ln, `[`](int c) `{ return IsAlpha(c) ? 0 : c; `});&] [s7; -|-|Mutex`::Lock `_`_(m);&] [s7; -|-|for(const auto`& s : h)&] [s7; -|-|-|w.FindAdd(s);&] [s7; -|`};&] [s7; `}&] [s7; co.Finish();&] [s7; &] [s7; DUMP(w);&] [s0; &] [s17; w `= `[Lorem, ipsum, dolor, sit, amet, consectetur, adipiscing, elit, sed, do, eiusmod, tempor, incididunt, ut, labore, et, dolore, magna, aliqua, Ut, enim, ad, minim, veniam, quis, nostrud, exercitation, ullamco, laboris, nisi, aliquip, ex, ea, commodo, consequat, esse, cillum, eu, fugiat, nulla, pariatur, Excepteur, Duis, aute, irure, in, reprehenderit, voluptate, velit, officia, deserunt, mollit, anim, id, est, laborum, sint, occaecat, cupidatat, non, proident, sunt, culpa, qui`]&] [s0; &] [s5; Adding words to [*C@5 w] requires [*C@5 Mutex]. Alternative to this `'result gathering`' [*C@5 Mutex] is [*C@5 CoWork`::FinLock]. The idea behind this is that CoWork requires an internal [*C@5 Mutex] to serialize access to common data, so why [*C@5 FinLock] locks this internal mutex a bit earlier, saving CPU cycles required to lock and unlock dedicated mutex. From API contract perspective, you can consider [*C@5 FinLock] to serialize code till the end of worker job.&] [s0; &] [s7; in.Seek(0);&] [s7; while(!in.IsEof()) `{&] [s7; -|String ln `= in.GetLine();&] [s7; -|co `& `[ln, `&w, `&m`] `{&] [s7; -|-|Vector h `= Split(ln, `[`](int c) `{ return IsAlpha(c) ? 0 : c; `});&] [s7; -|-|CoWork`::FinLock(); // replaces the mutex, locked till the end of CoWork job&] [s7; -|-|for(const auto`& s : h)&] [s7; -|-|-|w.FindAdd(s);&] [s7; -|`};&] [s7; `}&] [s7; co.Finish();&] [s7; &] [s7; DUMP(w);&] [s0; &] [s17; w `= `[Lorem, ipsum, dolor, sit, amet, consectetur, adipiscing, elit, sed, do, eiusmod, tempor, incididunt, ut, labore, et, dolore, magna, aliqua, Ut, enim, ad, minim, veniam, quis, nostrud, exercitation, ullamco, laboris, nisi, aliquip, ex, ea, commodo, consequat, esse, cillum, eu, fugiat, nulla, pariatur, Excepteur, Duis, aute, irure, in, reprehenderit, voluptate, velit, officia, deserunt, mollit, anim, id, est, laborum, sint, occaecat, cupidatat, non, proident, sunt, culpa, qui`]&] [s0; &] [s5; Of course, the code performed after [*C@5 FinLock] should not take long, otherwise there is negative impact on all [*C@5 CoWork] instances. In fact, from this perspective, above code is probably past the threshold...&] [s5; When exception is thrown in [*C@5 CoWork], it is propagated to the thread that calls [*C@5 Finish] and [*C@5 CoWork] is canceled. If more than single job throws, one of exceptions is selected randomly to be rethrown in Finish.&] [s5; As [*C@5 CoWork] destructor calls [*C@5 Finish] too, it is possible that it will be thrown by destructor, which is not exactly recommended thing to do in C`+`+, but is well defined and really the best option here:&] [s0; &] [s7; in.Seek(0);&] [s7; try `{&] [s7; -|while(!in.IsEof()) `{&] [s7; -|-|String ln `= in.GetLine();&] [s7; -|-|co `& `[ln, `&w, `&m`] `{&] [s7; -|-|-|if(ln.GetCount() > 75)&] [s7; -|-|-|-|throw `"Input line was too long!`";&] [s7; -|-|-|Vector h `= Split(ln, `[`](int c) `{ return IsAlpha(c) ? 0 : c; `});&] [s7; -|-|-|CoWork`::FinLock(); // replaces the mutex, locked till the end of CoWork job&] [s7; -|-|-|for(const auto`& s : h)&] [s7; -|-|-|-|w.FindAdd(s);&] [s7; -|-|`};&] [s7; -|`}&] [s7; -|co.Finish();&] [s7; `}&] [s7; catch(const char `*exception) `{&] [s7; -|DUMP(exception);&] [s7; `}&] [s0; &] [s5; Sometimes there is a need for cancellation of the whole [*C@5 CoWork]. [*C@5 Cancel] method cancels all scheduled jobs that have not been yet executed and sets [*C@5 CoWork] to canceled state, which can be checked in job routine using [*C@5 CoWork`::IsCanceled]:&] [s0; &] [s7; for(int i `= 0; i < 100; i`+`+)&] [s7; -|co `& `[`] `{&] [s7; -|-|for(;;) `{&] [s7; -|-|-|if(CoWork`::IsCanceled()) `{&] [s7; -|-|-|-|LOG(`"Job was canceled`");&] [s7; -|-|-|-|return;&] [s7; -|-|-|`}&] [s7; -|-|-|Sleep(1);&] [s7; -|-|`}&] [s7; -|`};&] [s7; Sleep(200); // Give CoWork a chance to start some jobs&] [s7; co.Cancel();&] [s0; &] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s17; Job was canceled&] [s0; &] [s5; Canceling CoWork is common in GUI applications.&] [s3;H4;:Section`_7`_5: 7.5 [C@5 AsyncWork]&] [s5; [*C@5 AsyncWork] is [*C@5 CoWork] based tool that resembles std`::future. [*C@5 AsyncWork] instances are created using [*C@5 Async] function and represent a work that can be done in parallel with current thread. [*C@5 AsyncWork] supports returning values. A call to [*C@5 AsyncWork`::Get] makes sure that a work routine was finished and returns the return value (if any):&] [s0; &] [s7; auto a `= Async(`[`](int n) `-> double `{&] [s7; -|double f `= 1;&] [s7; -|for(int i `= 2; i <`= n; i`+`+)&] [s7; -|-|f `*`= i;&] [s7; -|return f;&] [s7; `}, 100);&] [s7; &] [s7; DUMP(a.Get());&] [s0; &] [s17; a.Get() `= 9.33262154439441e157&] [s0; &] [s5; Exceptions thrown in Async work are propagated upon call to [*C@5 Get]:&] [s0; &] [s7; auto b `= Async(`[`] `{ throw `"error`"; `});&] [s7; &] [s7; try `{&] [s7; -|b.Get();&] [s7; `}&] [s7; catch(...) `{&] [s7; -|LOG(`"Exception has been caught`");&] [s7; `}&] [s0; &] [s17; Exception has been caught&] [s0; &] [s5; [*C@5 AsyncWork] instances can be canceled (and are canceled in destructor if Get is not called on them):&] [s0; &] [s7; `{&] [s7; -|auto c `= Async(`[`] `{&] [s7; -|-|for(;;)&] [s7; -|-|-|if(CoWork`::IsCanceled()) `{&] [s7; -|-|-|-|LOG(`"Work was canceled`");&] [s7; -|-|-|-|break;&] [s7; -|-|-|`}&] [s7; -|`});&] [s7; -|Sleep(100); // give it chance to start&] [s7; -|// c destructor cancels the work (can be explicitly canceled by Cancel method too)&] [s7; `}&] [s0; &] [s17; Work was canceled&] [s0; &] [s3;H4;:Section`_7`_6: 7.6 CoPartition&] [s5; There is some overhead associated with CoWork worker threads. That is why e.g. performing a simple operation on the array spawning worker thread for each element is not a good idea performance wise:&] [s0; &] [s7; Vector data;&] [s7; for(int i `= 0; i < 10000; i`+`+)&] [s7; -|data.Add(i);&] [s7; &] [s7; int sum `= 0;&] [s7; &] [s7; CoWork co;&] [s7; for(int i `= 0; i < data.GetCount(); i`+`+)&] [s7; -|co `& `[i, `&sum, `&data`] `{ CoWork`::FinLock(); sum `+`= data`[i`]; `};&] [s7; co.Finish();&] [s7; DUMP(sum);&] [s0; &] [s17; sum `= 49995000&] [s0; &] [s5; Above code computes the sum of all elements in the [*C@5 Vector], using CoWorker job for each element. While producing the correct result, it is likely to run much slower than single`-threaded version.&] [s5; The solution to the problem is to split the array into small number of larger subranges that are processed in parallel. This is what [*C@5 CoPartition] template algorithm does:&] [s0; &] [s7; sum `= 0;&] [s7; CoPartition(data, `[`&sum`](const auto`& subrange) `{&] [s7; -|int partial`_sum `= 0;&] [s7; -|for(const auto`& x : subrange)&] [s7; -|-|partial`_sum `+`= x;&] [s7; -|CoWork`::FinLock(); // available as CoPartition uses CoWork&] [s7; -|sum `+`= partial`_sum;&] [s7; `});&] [s7; DUMP(sum);&] [s0; &] [s17; sum `= 49995000&] [s0; &] [s5; Note that CoWork is still internally used, so [*C@5 CoWork`::FinLock] is available. Instead of working on subranges, it is also possible to use iterators:&] [s0; &] [s7; sum `= 0;&] [s7; CoPartition(data.begin(), data.end(), `[`&sum`] (auto l, auto h) `{&] [s7; -|int partial`_sum `= 0;&] [s7; -|while(l !`= h)&] [s7; -|-|partial`_sum `+`= `*l`+`+;&] [s7; -|CoWork`::FinLock(); // available as CoPartition uses CoWork&] [s7; -|sum `+`= partial`_sum;&] [s7; `});&] [s7; DUMP(sum);&] [s0; &] [s17; sum `= 49995000&] [s0; &] [s5; There is no requirement on the type of iterators, so it is even possible to use just indices:&] [s0; &] [s7; sum `= 0;&] [s7; CoPartition(0, data.GetCount(), `[`&sum, `&data`] (int l, int h) `{&] [s7; -|int partial`_sum `= 0;&] [s7; -|while(l !`= h)&] [s7; -|-|partial`_sum `+`= data`[l`+`+`];&] [s7; -|CoWork`::FinLock(); // available as CoPartition uses CoWork&] [s7; -|sum `+`= partial`_sum;&] [s7; `});&] [s7; DUMP(sum);&] [s0; &] [s17; sum `= 49995000&] [s0; &] [s3;H4;:Section`_7`_7: 7.7 CoDo&] [s5; An alternative to [*C@5 CoPartition] is [*C@5 CoDo]. In this pattern, the job is simply started in all threads and the code is responsible for scheduling the work. [*C@5 CoDo] waits for all started threads to finish. Scheduling is the responsibility of client code, but can be easily managed using the std`::atomic counter. This way, the overhead associated with creating lambdas and scheduling them is kept to the minimum (basically the cost of atomic increment). Once again, CoDo is based on CoWork, so [*C@5 CoWork`::FinLock] is available.&] [s0; &] [s7; Vector data;&] [s7; for(int i `= 0; i < 100; i`+`+)&] [s7; -|data.Add(AsString(1.0 / i));&] [s7; &] [s7; double sum `= 0;&] [s7; &] [s7; std`::atomic ii(0);&] [s7; &] [s7; CoDo(`[`&`] `{&] [s7; -|double m `= 0;&] [s7; -|for(int i `= ii`+`+; i < data.GetCount(); i `= ii`+`+)&] [s7; -|-|m `+`= atof(data`[i`]);&] [s7; -|CoWork`::FinLock();&] [s7; -|sum `+`= m;&] [s7; `});&] [s7; &] [s7; DUMP(sum);&] [s0; &] [s17; sum `= 5.17737751763962&] [s0; &] [s3;H4;:Section`_7`_8: 7.8 Parallel algorithms&] [s5; U`+`+ provides a parallel versions of algorithms where it makes sense. The naming scheme is `'Co`' prefix before the name of algorithm designates the parallel version.&] [s5; So the parallel version of e.g. [*C@5 FindIndex] is [*C@5 CoFindIndex], for [*C@5 Sort] it is [*C@5 CoSort]:&] [s0; &] [s7; Vector x`{ `"zero`", `"one`", `"two`", `"three`", `"four`", `"five`" `};&] [s7; &] [s7; DUMP(FindIndex(x, `"two`"));&] [s7; DUMP(CoFindIndex(x, `"two`"));&] [s7; &] [s7; CoSort(x);&] [s7; DUMP(x);&] [s0; &] [s17; FindIndex(x, `"two`") `= 2&] [s17; CoFindIndex(x, `"two`") `= 2&] [s17; x `= `[five, four, one, three, two, zero`]&] [s0; &] [s5; Caution should be exercised when using these algorithms `- for small datasets, they are almost certainly slower than single`-threaded versions.&] [s5; ]]