ultimatepp/uppsrc/Core/srcdoc.tpp/Tutorial$en-us.tpp
cxl 1e89b14fcd CodeEditor, ide: ReadOnly mode now blocks all write operations (some of advanced ones were not blocked)
git-svn-id: svn://ultimatepp.org/upp/trunk@11112 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2017-05-22 09:57:05 +00:00

3216 lines
No EOL
95 KiB
C++

topic "U++ Containers Tutorial";
[2 $$0,0#00000000000000000000000000000000:Default]
[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]
[{_}
[s2;%% U`+`+ Core Tutorial&]
[s22;H6; [7 1. Basics]&]
[s3; 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 0x01CAF9D8, size 0x3 `= 3&]
[s17; `+0 0x01CAF9D8 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, 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; 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 camn concatenate 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 0x01CAFA38, size 0x5 `= 5&]
[s17; `+0 0x01CAFA38 31 32 33 34 00
1234. &]
[s0; &]
[s3;H4; 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; 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; 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 `= 02/21/2017&]
[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 `= 2017&]
[s17; (int)date.month `= 2&]
[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 `= 02/22/2017&]
[s17; `-`-date `= 02/20/2017&]
[s17; `+`+date `= 02/21/2017&]
[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) `= 6261&]
[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) `= 2&]
[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) `= 02/28/2017&]
[s17; FirstDayOfMonth(date) `= 02/01/2017&]
[s17; LastDayOfYear(date) `= 12/31/2017&]
[s17; FirstDayOfYear(date) `= 01/01/2017&]
[s17; DayOfYear(date) `= 52&]
[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) `= 10/21/2018&]
[s17; GetMonths(date, date `+ 100) `= 3&]
[s17; GetMonthsP(date, date `+ 100) `= 4&]
[s17; AddYears(date, 2) `= 02/21/2019&]
[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 `= 02/21/2017 11:17:59&]
[s17; (Date)time `= 02/21/2017&]
[s17; (int)time.hour `= 11&]
[s17; (int)time.minute `= 17&]
[s17; (int)time.second `= 59&]
[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 `= 02/21/2017 11:18:00&]
[s17; time `+ 24 `* 3600 `= 02/22/2017 11:17:59&]
[s17; time `- date `= 0&]
[s17; time `- ToTime(date) `= 40679&]
[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; 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 02/21/2017 02/21/2017
11:17:59&]
[s17; sout `= 1.23 02/21/2017 02/21/2017 11:17:59&]
[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; 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) `= 4272824901&]
[s0; &]
[s0; &]
[s7; x.a << `'!`';&]
[s7; &]
[s7; DUMP(GetHashValue(x));&]
[s0; &]
[s17; GetHashValue(x) `= 3378606405&]
[s0; &]
[s3;H4; 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; 2. Array containers&]
[s3; 2.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; 2.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; 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 `= `[1025, 972, 1004, 1033, 996, 988, 992, 1022, 970, 998`]&]
[s0; &]
[s5; You can apply algorithms on containers, e.g. Sort&]
[s0; &]
[s7; Sort(v);&]
[s7; &]
[s7; DUMP(v);&]
[s0; &]
[s17; v `= `[970, 972, 988, 992, 996, 998, 1004, 1022, 1025, 1033`]&]
[s0; &]
[s3;H4; 2.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; 2.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: `[2020, 1997, 2005, 2018, 1960`]&]
[s17; -|`[1`] `= Test 6: `[1708, 1680, 1730, 1617, 1671, 1594`]&]
[s17; -|`[2`] `= Test 7: `[1380, 1489, 1479, 1424, 1415, 1345, 1468`]&]
[s17; -|`[3`] `= Test 8: `[1332, 1239, 1185, 1245, 1236, 1305, 1247,
1211`]&]
[s17; -|`[4`] `= Test 9: `[1122, 1129, 1109, 1083, 1151, 1115, 1069,
1113, 1109`]&]
[s17; -|`[5`] `= Test 10: `[1031, 1011, 1124, 1001, 980, 940, 973, 943,
1015, 982`]&]
[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; 2.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; 2.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; 2.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; 2.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, foo, delta, kappa, one, two, three`]&]
[s17; ndx.Find(`"foo`") `= 2&]
[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, foo, delta, 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, foo, delta, one, two, three`]&]
[s0; &]
[s0; &]
[s7; ndx.RemoveKey(`"two`");&]
[s7; &]
[s7; DUMP(ndx);&]
[s0; &]
[s17; ndx `= `[alfa, foo, delta, one, three`]&]
[s0; &]
[s0; &]
[s7; ndx.Insert(0, `"insert`");&]
[s7; &]
[s7; DUMP(ndx);&]
[s0; &]
[s17; ndx `= `[insert, alfa, foo, delta, 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, foo, delta, 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, foo, delta, one, three`]&]
[s0; &]
[s3;H4; 2.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; 2.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`& k : m.GetKeys())&]
[s7; -|DUMP(p);&]
[s0; &]
[s17; p `= Peter Carpenter&]
[s17; p `= Peter Carpenter&]
[s17; p `= Peter Carpenter&]
[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 `'unliked`' 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; 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 Engles&]
[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 Engles&]
[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 Engles&]
[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, 22: Ali Baba, 3: Peter Carpenter, 44: Ivan
Wilks`}&]
[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, Ali Baba, Peter Carpenter, Ivan Wilks`]&]
[s17; ks `= `[1, 22, 3, 44`]&]
[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, 22: Ali Baba, 3: Peter Carpenter,
44: Ivan Wilks`}&]
[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; 2.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 class 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; MakeOne<Derived2> d2;&]
[s7; DUMP(d2`->Get());&]
[s0; &]
[s17; d2`->Get() `= Derived2&]
[s0; &]
[s0; &]
[s7; s `= pick(d2);&]
[s7; DUMP(s`->Get());&]
[s0; &]
[s17; s`->Get() `= Derived2&]
[s0; &]
[s3;H4; 2.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; 2.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; 2.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; 2.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) `= 834842890&]
[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; 3. Ranges and algoritims&]
[s3; 3.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() `= int&]
[s17; typeid(ValueTypeOf<decltype(SubRange(x, 1, 1))>).name() `= int&]
[s17; typeid(IteratorOf<decltype(x)>).name() `= int `*&]
[s17; typeid(ConstIteratorOf<decltype(SubRange(x, 1, 1))>).name()
`= int `*&]
[s17; typeid(SubRangeOf<Vector<int>>).name() `= class Upp`::SubRangeClass<int
`*>&]
[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 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; 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; &]
[s3;H4; 3.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; 3.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; 4. Value&]
[s3; 4.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 `= 02/21/2017&]
[s17; d `= hello&]
[s17; x `= 1&]
[s17; y `= 2.34&]
[s17; z `= 02/21/2017&]
[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 `= 02/21/2017 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; 4.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; 4.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; 4.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; 5. Function and lambdas&]
[s3; 5.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; 5.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; 6. Multithreading&]
[s3; 6.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; 6.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 `= 1022929&]
[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; 6.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; 6.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, non, proident, sunt, in, culpa, qui,
officia, deserunt, mollit, anim, id, est, laborum, quis, nostrud,
exercitation, ullamco, laboris, nisi, ut, aliquip, ex, ea, commodo,
consequat, Duis, aute, irure, reprehenderit, voluptate, velit,
tempor, incididunt, labore, et, dolore, magna, aliqua, Ut, enim,
ad, minim, veniam, esse, cillum, eu, fugiat, nulla, pariatur,
Excepteur, sint, occaecat, cupidatat`]&]
[s0; &]
[s5; Adding words to [*C@5 w] requires [*C@5 Mutex]. Alternative to this
`'result gathering`' [*C@5 Mutex] is 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, non, proident, sunt, in, culpa, qui,
officia, deserunt, mollit, anim, id, est, laborum, quis, nostrud,
exercitation, ullamco, laboris, nisi, ut, aliquip, ex, ea, commodo,
consequat, Duis, aute, irure, reprehenderit, voluptate, velit,
tempor, incididunt, labore, et, dolore, magna, aliqua, Ut, enim,
ad, minim, veniam, esse, cillum, eu, fugiat, nulla, pariatur,
Excepteur, sint, occaecat, cupidatat`]&]
[s0; &]
[s5; Of course, the code performed after FinLock should not take
long, otherwise there is negative impact on all CoWork instances.
In fact, from this perspective, above code is probably past this
threshold...&]
[s3;H4; 6.5 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
reason, 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 CoPartition 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; 6.6 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 `'Sort`' it is `'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.]]