mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-16 06:05:58 -06:00
3830 lines
No EOL
119 KiB
C++
3830 lines
No EOL
119 KiB
C++
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<int> 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<int, String> 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, `"<inserted>`");&]
|
|
[s7; DUMP(a);&]
|
|
[s0; &]
|
|
[s17; a `= 012345<inserted>6789&]
|
|
[s0; &]
|
|
[s0; &]
|
|
[s7; a.Remove(2, 2);&]
|
|
[s7; DUMP(a);&]
|
|
[s0; &]
|
|
[s17; a `= 0145<inserted>6789&]
|
|
[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) `= 5<i&]
|
|
[s17; a.Mid(3) `= 5<inserted>6789&]
|
|
[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 <class T>&]
|
|
[s7; -|inline String AsString(const T`& x)&]
|
|
[s7; -|`{&]
|
|
[s7; -|-|return x.ToString();&]
|
|
[s7; -|`}&]
|
|
[s7; -|&]
|
|
[s7; -|template <class T>&]
|
|
[s7; -|inline Stream`& operator<<(Stream`& s, const T`& x)&]
|
|
[s7; -|`{&]
|
|
[s7; -|-|s << AsString(x);&]
|
|
[s7; -|-|return s;&]
|
|
[s7; -|`}&]
|
|
[s7; -|&]
|
|
[s7; -|template <class T>&]
|
|
[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<Foo3> `{&]
|
|
[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<Foo4> `{&]
|
|
[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<int> 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<int> 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<int> v`{ 1, 2 `};&]
|
|
[s7; &]
|
|
[s7; DUMP(v);&]
|
|
[s7; &]
|
|
[s7; Vector<int> 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<Vector<int>> 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<std`::vector<int>> sv;&]
|
|
[s7; sv.push`_back(`{1, 2, 3`});&]
|
|
[s7; for(auto i : sv) // invokes std`::vector<int> 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<Vector<int>> 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<Distribution> `{&]
|
|
[s7; -|String text;&]
|
|
[s7; -|Vector<int> 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<int>``
|
|
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<Distribution> 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<std`::string> 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<Number> num;&]
|
|
[s7; num.Create<Double>().n `= 15.5;&]
|
|
[s7; num.Create<Integer>().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<int> 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<Val> num;&]
|
|
[s7; num.CreateHead<Number>().n `= 3;&]
|
|
[s7; num.CreateTail<Text>().s `= `"Hello`";&]
|
|
[s7; num.CreateHead<Text>().s `= `"World`";&]
|
|
[s7; num.CreateTail<Number>().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<String> 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<String> 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<Person> `{&]
|
|
[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<Person> 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<Person> `{&]
|
|
[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<String, Person> 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<Person> ps `= m.PickValues();&]
|
|
[s7; Vector<String> 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<String, Person>(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<String, Person> am;&]
|
|
[s7; am.Create<Person>(`"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<Base> 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<Derived1>();&]
|
|
[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<Derived1>());&]
|
|
[s7; DUMP(s.Is<Base>());&]
|
|
[s7; DUMP(s.Is<Derived2>());&]
|
|
[s0; &]
|
|
[s17; s.Is<Derived1>() `= true&]
|
|
[s17; s.Is<Base>() `= true&]
|
|
[s17; s.Is<Derived2>() `= 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<Derived1>();&]
|
|
[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<String>() `= `"Hello!`";&]
|
|
[s7; -|else&]
|
|
[s7; -|-|x.Create<Color>() `= Blue();&]
|
|
[s7; -|&]
|
|
[s7; -|if(x.Is<String>())&]
|
|
[s7; -|-|LOG(`"Any is now String: `" << x.Get<String>());&]
|
|
[s7; -|&]
|
|
[s7; -|if(x.Is<Color>())&]
|
|
[s7; -|-|LOG(`"Any is now Color: `" << x.Get<Color>());&]
|
|
[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<int> 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<int> 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<String, int> 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<int, String, String> 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<int>());&]
|
|
[s0; &]
|
|
[s17; x.Get<1>() `= hello&]
|
|
[s17; x.Get<int>() `= 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<int, String, String> 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<double, String, String> 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<int, const char `*> 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<int> 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<decltype(x)>).name());&]
|
|
[s7; DUMP(typeid(ValueTypeOf<decltype(SubRange(x, 1, 1))>).name());&]
|
|
[s7; DUMP(typeid(IteratorOf<decltype(x)>).name());&]
|
|
[s7; DUMP(typeid(ConstIteratorOf<decltype(SubRange(x, 1, 1))>).name());&]
|
|
[s7; DUMP(typeid(SubRangeOf<Vector<int>>).name());&]
|
|
[s0; &]
|
|
[s17; typeid(ValueTypeOf<decltype(x)>).name() `= i&]
|
|
[s17; typeid(ValueTypeOf<decltype(SubRange(x, 1, 1))>).name() `= i&]
|
|
[s17; typeid(IteratorOf<decltype(x)>).name() `= Pi&]
|
|
[s17; typeid(ConstIteratorOf<decltype(SubRange(x, 1, 1))>).name()
|
|
`= Pi&]
|
|
[s17; typeid(SubRangeOf<Vector<int>>).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<int> 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<int> 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<int> 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<int> 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<String> 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<int> a `{ 5, 10, 2, 9, 7, 3 `};&]
|
|
[s7; Vector<String> 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<int> 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<Point> 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<int>());&]
|
|
[s7; DUMP(a.Is<double>());&]
|
|
[s7; DUMP(b.Is<double>());&]
|
|
[s7; DUMP(c.Is<int>());&]
|
|
[s7; DUMP(c.Is<Date>());&]
|
|
[s7; DUMP(d.Is<String>());&]
|
|
[s0; &]
|
|
[s17; a.Is<int>() `= true&]
|
|
[s17; a.Is<double>() `= false&]
|
|
[s17; b.Is<double>() `= true&]
|
|
[s17; c.Is<int>() `= false&]
|
|
[s17; c.Is<Date>() `= true&]
|
|
[s17; d.Is<String>() `= 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<RawFoo>());&]
|
|
[s0; &]
|
|
[s17; q.Is<RawFoo>() `= 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<RawFoo>().x);&]
|
|
[s0; &]
|
|
[s17; q.To<RawFoo>().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<Foo, 10010> `{&]
|
|
[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<Foo>();
|
|
`}&]
|
|
[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<Foo>(); // 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<ValueArray>()); // 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<ValueArray>() `= 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<Value>] 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<int (int)> 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<int> 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<int, const String`&> 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<int> x`{ 1, 2 `};&]
|
|
[s7; Array<String> 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<int> 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<void
|
|
()>] 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<String> 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<String> 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<String> 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<String> 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<int> 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<String> 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<int> 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<String> 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; ]] |