Movatterモバイル変換


[0]ホーム

URL:




Chapter 18: The Standard Template Library

TheStandard Template Library (STL) is a general purpose libraryconsisting of containers, generic algorithms, iterators, function objects,allocators, adapters and data structures. The data structures used by thealgorithms areabstract in the sense that the algorithms can be used with(practically) any data type.

The algorithms can process these abstract data types because they aretemplate based. This chapter does not cover templateconstruction(see chapter21 for that). Rather, it focuses on theuseof the algorithms.

Several elements also used by the standard template library have already beendiscussed in theC++ Annotations. In chapter12 abstract containerswere discussed, and in section11.11 function objects were introduced.Also,iterators were mentioned at several places in this document.

The main components of the STL are covered in this and the nextchapter. Iterators, adapters, smart pointers, multi threading and otherfeatures of the STL are discussed in coming sections. Genericalgorithms are covered in the next chapter (19).

Allocators take care of the memory allocation within theSTL. The defaultallocator class suffices for most applications, and isnot further discussed in theC++ Annotations.

All elements of the STL are defined in thestandard namespace. Therefore, ausing namespace std or a comparabledirective is required unless it is preferred to specify the required namespaceexplicitly. In header files thestd namespace should explicitlybe used (cf. section7.11.1).

In this chapter the emptyangle bracket notation is frequently used. Incode a typename must be supplied between the angle brackets. E.g.,plus<>is used in theC++ Annotations, but in codeplus<string> may be encountered.

18.1: Predefined function objects

Before using the predefined function objects presented in this section the<functional> header file must be included.

Function objects play important roles in genericalgorithms. For example, there exists a generic algorithmsortexpecting two iterators defining the range of objects that should be sorted,as well as a function object calling the appropriate comparison operator fortwo objects. Let's take a quick look at this situation. Assume strings arestored in a vector, and we want to sort the vector in descending order. Inthat case, sorting the vectorstringVec is as simple as:

        sort(stringVec.begin(), stringVec.end(), greater<string>());

The last argument is recognized as aconstructor: it is aninstantiation of thegreater<> class template, applied tostrings. This object is called as a function object by thesortgeneric algorithm. The generic algorithm calls the function object'soperator() member to compare twostring objects. The function object'soperator() will, in turn, calloperator> of thestring datatype. Eventually, whensort returns, the first element of the vector willcontain the string having the greateststring value of all.

The function object'soperator() itself isnot visible at thispoint. Don't confuse the parentheses in the `greater<string>()' argumentwith callingoperator(). Whenoperator() is actually used insidesort, it receives two arguments: two strings to compare for`greaterness'. Sincegreater<string>::operator() is definedinline, thecall itself is not actually present in the abovesort call. Insteadsort callsstring::operator> throughgreater<string>::operator().

Now that we know that a constructor is passed as argument to (many) genericalgorithms, we can design our own function objects. Assume we want to sort ourvector case-insensitively. How do we proceed? First we note that the defaultstring::operator< (for an incremental sort) is not appropriate, as it doescase sensitive comparisons. So, we provide our ownCaseInsensitive class,which compares two strings case insensitively. Using thePOSIX functionstrcasecmp, the following program performs the trick. Itcase-insensitively sorts its command-line arguments in ascending alphabeticorder:

    #include <iostream>    #include <string>    #include <cstring>    #include <algorithm>    using namespace std;    class CaseInsensitive    {        public:            bool operator()(string const &left, string const &right) const            {                return strcasecmp(left.c_str(), right.c_str()) < 0;            }    };    int main(int argc, char **argv)    {        sort(argv, argv + argc, CaseInsensitive{});        for (int idx = 0; idx < argc; ++idx)            cout << argv[idx] << " ";        cout << '\n';    }
The default constructor of theclass CaseInsensitive is used toprovidesort with its final argument. So the only member function thatmust be defined isCaseInsensitive::operator(). Since we know it's calledwithstring arguments, we define it to expect twostring arguments,which are used when callingstrcasecmp. Furthermore, the function calloperatoroperator() is definedinline, so that it does not produceoverhead when called by thesort function. Thesort function calls thefunction object with various combinations ofstrings. If the compilergrants our inline requests, it will in fact callstrcasecmp, skipping twoextra function calls.

The comparison function object is often apredefined function object.Predefined function object classes are available for many commonly usedoperations. In the following sections the available predefined functionobjects are presented, together with some examples showing their use. Near theend of the section about function objectsfunction adapters areintroduced.

Predefined function objects are used predominantly with genericalgorithms. Predefined function objects exists for arithmetic, relational, andlogical operations.

18.1.1: Arithmetic function objects

The arithmeticfunction objects support the standardarithmetic operations: addition,subtraction, multiplication, division, modulo and negation. These functionobjects invoke the corresponding operators of the data types for which theyare instantiated. For example, for addition the function objectplus<Type> is available. If we replaceType bysize_t then the additionoperator forsize_t values is used, if we replaceType bystring,the addition operator for strings is used. For example:
    #include <iostream>    #include <string>    #include <functional>    using namespace std;    int main(int argc, char **argv)    {        plus<size_t> uAdd;              // function object to add size_ts        cout << "3 + 5 = " << uAdd(3, 5) << '\n';        plus<string> sAdd;              // function object to add strings        cout << "argv[0] + argv[1] = " << sAdd(argv[0], argv[1]) << '\n';    }    /*        Output when called as: a.out going        3 + 5 = 8        argv[0] + argv[1] = a.outgoing    */
Why is this useful? Note that the function object can be used with allkinds of data types (not only with the predefined datatypes) supporting theoperator called by the function object.

Suppose we want to perform an operation on a left hand side operand which isalways the same variable and a right hand side argument for which, in turn,all elements of an array should be used. E.g., we want to compute the sum ofall elements in an array; or we want to concatenate all the strings in atext-array. In situations like these function objects come in handy.

As stated, function objects are heavily used in the context of the genericalgorithms, so let's take a quick look ahead at yet another one.

The generic algorithmaccumulate visits all elements specified by aniterator-range, and performs a requested binary operation on a common elementand each of the elements in the range, returning the accumulated result aftervisiting all elements specified by the iterator range. It's easy to use thisalgorithm. The next program accumulates all command line arguments and printsthe final string:

    #include <iostream>    #include <string>    #include <functional>    #include <numeric>    using namespace std;    int main(int argc, char **argv)    {        string result =                accumulate(argv, argv + argc, string{}, plus<string>());        cout << "All concatenated arguments: " << result << '\n';    }
The first two arguments define the (iterator) range of elements to visit,the third argument isstring. This anonymous string object provides aninitial value. We could also have used
    "All concatenated arguments: "s

in which case thecout statement could simply have beencout << result << '\n'. The string-addition operation is used, called fromplus<string>. Thefinal concatenated string is returned.

Now we define a classTime, overloadingoperator+. Again, we canapply the predefined function objectplus, now tailored to our newlydefined datatype, to add times:

    #include <iostream>    #include <string>    #include <vector>    #include <functional>    #include <numeric>    using namespace std;    class Time    {        friend ostream &operator<<(ostream &str, Time const &time);        size_t d_days;        size_t d_hours;        size_t d_minutes;        size_t d_seconds;        public:            Time(size_t hours, size_t minutes, size_t seconds);            Time &operator+=(Time const &rhs);    };    Time operator+(Time const &lhs, Time const &rhs)    {        Time ret(lhs);        return ret += rhs;    }    Time::Time(size_t hours, size_t minutes, size_t seconds)    :        d_days(0),        d_hours(hours),        d_minutes(minutes),        d_seconds(seconds)    {}    Time &Time::operator+=(Time const &rhs)    {        d_seconds   += rhs.d_seconds;        d_minutes   += rhs.d_minutes   + d_seconds / 60;        d_hours     += rhs.d_hours     + d_minutes / 60;        d_days      += rhs.d_days      + d_hours   / 24;        d_seconds   %= 60;        d_minutes   %= 60;        d_hours     %= 24;        return *this;    }    ostream &operator<<(ostream &str, Time const &time)    {        return str << time.d_days << " days, " << time.d_hours <<                                                    " hours, " <<                      time.d_minutes << " minutes and " <<                      time.d_seconds << " seconds.";    }    int main(int argc, char **argv)    {        vector<Time> tvector;        tvector.push_back(Time( 1, 10, 20));        tvector.push_back(Time(10, 30, 40));        tvector.push_back(Time(20, 50,  0));        tvector.push_back(Time(30, 20, 30));        cout <<            accumulate            (                tvector.begin(), tvector.end(), Time(0, 0, 0), plus<Time>()            ) <<            '\n';    }    //  Displays: 2 days, 14 hours, 51 minutes and 30 seconds.
The design of the above program is fairly straightforward.Timedefines a constructor, it defines an insertion operator and it defines its ownoperator+, adding two time objects. Inmain fourTime objects arestored in avector<Time> object. Then,accumulate is used to computethe accumulated time. It returns aTime object, which is inserted intocout.

While this section's first example illustrated using anamed functionobject, the last two examples illustrate howanonymous objects can bepassed to the (accumulate) function.

The STL supports the following set of arithmetic function objects. Thefunction call operator (operator()) of these function objects calls thematching arithmetic operator for the objects that are passed to the functioncall operator, returning that arithmetic operator's return value. Thearithmetic operator that is actually called is mentioned below:

In the next example thetransform generic algorithm is used to togglethe signs of all elements of an array.Transformexpects two iterators, defining the range of objects to be transformed; aniterator defining the begin of the destination range (which may be the sameiterator as the first argument); and a function object defining a unaryoperation for the indicated data type.
    #include <iostream>    #include <string>    #include <functional>    #include <algorithm>    using namespace std;    int main(int argc, char **argv)    {        int iArr[] = { 1, -2, 3, -4, 5, -6 };        transform(iArr, iArr + 6, iArr, negate<int>());        for (int idx = 0; idx < 6; ++idx)            cout << iArr[idx] << ", ";        cout << '\n';    }    // Displays:  -1, 2, -3, 4, -5, 6,

18.1.2: Relational function objects

The relational operators are called by the relationalfunction objects. All standard relational operators are supported:==, !=,>, >=, < and<=.

The STL supports the following set of relational function objects. Thefunction call operator (operator()) of these function objects calls thematching relational operator for the objects that are passed to the functioncall operator, returning that relational operator's return value. Therelational operator that is actually called is mentioned below:

An example using the relational function objects in combination withsort is:
    #include <iostream>    #include <string>    #include <functional>    #include <algorithm>    using namespace std;    int main(int argc, char **argv)    {        sort(argv, argv + argc, greater_equal<string>());        for (int idx = 0; idx < argc; ++idx)            cout << argv[idx] << " ";        cout << '\n';        sort(argv, argv + argc, less<string>());        for (int idx = 0; idx < argc; ++idx)            cout << argv[idx] << " ";        cout << '\n';    }
The example illustrates how strings may be sorted alphabetically andreversed alphabetically. By passinggreater_equal<string> the strings aresorted indecreasing order (the first word will be the 'greatest'), bypassingless<string> the strings are sorted inincreasing order (thefirst word will be the 'smallest').

Note thatargv containschar * values, and that the relationalfunction object expects astring. The promotion fromchar const * tostring is silently performed.

18.1.3: Logical function objects

Thelogical operators are called by the logical functionobjects. The standard logical operators are supported:and, or, andnot.

The STL supports the following set of logical function objects. Thefunction call operator (operator()) of these function objects calls thematching logical operator for the objects that are passed to the functioncall operator, returning that logical operator's return value. Thelogical operator that is actually called is mentioned below:

An example usingoperator! is provided in the following trivialprogram, usingtransform to transformthe logical values stored in an array:
    #include <iostream>    #include <string>    #include <functional>    #include <algorithm>    using namespace std;    int main(int argc, char **argv)    {        bool bArr[] = {true, true, true, false, false, false};        size_t const bArrSize = sizeof(bArr) / sizeof(bool);        for (size_t idx = 0; idx < bArrSize; ++idx)            cout << bArr[idx] << " ";        cout << '\n';        transform(bArr, bArr + bArrSize, bArr, logical_not<bool>());        for (size_t idx = 0; idx < bArrSize; ++idx)            cout << bArr[idx] << " ";        cout << '\n';    }    /*      Displays:        1 1 1 0 0 0        0 0 0 1 1 1    */

18.1.4: The `std::not_fn' negator

Anegator is a function object toggling the truth value ofa function that's called from the negator: if the function returnstruethe negator returnsfalse and vv.

The standard negator isstd::not_fn, declared in the<functional> header file.

The functionnot_fn expects a (movable) object as its argument, returningthe negated value of the return value of its argument's function calloperator.

As an example consider amain function defining an array ofintvalues:

    int main()    {        int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};    }

To count the number of even valuescount_if, using a lambda function canbe used:

        cout <<             count_if(arr, arr + size(arr),                [&](int value)                {                    return (value & 1) == 0;                }            ) << '\n';

To count the number of odd values,not_fn can be used in the above codelike so:

        cout <<             count_if(arr, arr + size(arr),                not_fn(                    [&](int value)                    {                        return (value & 1) == 0;                    }                )            ) << '\n';

Of course, in this simple example the lambda function could also easily havebeen modified. But if instead of a lambda function an existing classimplementing a function object had been used it would have been difficult orimpossible to change the behavior of that class. If the class offers movingoperations thennot_fn can be used to negate the values returned by thatclass's function call operator.

18.2: Iterators

In addition to the conceptual iterator types presented in this section the STLdefines several adapters allowing objects tobe passed as iterators. These adapters are presented in the upcomingsections. Before those adapters can be used the<iterator> header filemust be included.

The standard iterator (std::iterator) isnowdeprecated (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0174r1.html#2.1),and the compiler issues a corresponding warning. Consequently,std::iterator should no longer be used when designing your own iterators(section22.14 describes how to design your own).

Iterators are objects acting like pointers. Iterators have the followinggeneral characteristics:

STL containers usually define members offering iterators (i.e., theydefine their own typeiterator). These members are commonly calledbegin andend and (for reversed iterators (typereverse_iterator))rbegin andrend.

Whereas reverse iterators can be constructed from ordinary (forward)iterators usingreverse_iterator constructors as in:

    string str;    auto revit = string::reverse_iterator{ str.begin() };

the opposite is not accomplished that way. To retrieve the forwarditerator corresponding to a reverse iterator, thereverse_iterator.base()member can be used. E.g., to obtain the forward iterator corresponding torevit use

    auto forward { revit.base() };

Standard practice requires iterator ranges to beleft inclusive. The notation[left, right) indicates thatleftis an iterator pointing to the first element, whileright is an iteratorpointing justbeyond the last element. The iterator range isemptywhenleft == right.

The following example shows how all elements of a vector of strings can beinserted intocout using its iterator ranges[begin(), end()), and[rbegin(), rend()). Note that thefor-loops for both ranges areidentical. Furthermore it nicely illustrates how theauto keyword can beused to define the type of the loop control variable instead of using a muchmore verbose variable definition likevector<string>::iterator (see alsosection3.3.7):

    #include <iostream>    #include <vector>    #include <string>    using namespace std;    int main(int argc, char **argv)    {        vector<string> args(argv, argv + argc);        for (auto iter = args.begin(); iter != args.end(); ++iter)            cout << *iter << " ";        cout << '\n';        for (auto iter = args.rbegin(); iter != args.rend(); ++iter)            cout << *iter << " ";        cout << '\n';    }

Furthermore, the STL definesconst_iterator types that must be usedwhen visiting a series of elements in a constant container. Whereas theelements of the vector in the previous example could have been altered, theelements of the vector in the next example are immutable, andconst_iterators are required:

    #include <iostream>    #include <vector>    #include <string>    using namespace std;    int main(int argc, char **argv)    {        vector<string> const args(argv, argv + argc);        for        (            vector<string>::const_iterator iter = args.begin();                iter != args.end();                    ++iter        )            cout << *iter << " ";        cout << '\n';        for        (            vector<string>::const_reverse_iterator iter = args.rbegin();                iter != args.rend();                    ++iter        )            cout << *iter << " ";        cout << '\n';    }
The examples also illustrate that plain pointers can be used as iterators. Theinitializationvector<string> args(argv, argv + argc) provides theargs vector with a pair of pointer-based iterators:argv points to thefirst element to initializeargs with,argv + argc points just beyondthe last element to be used,++argv reaches the next command lineargument. This is a general pointer characteristic, which is why they too canbe used in situations whereiterators are expected.

The STL defines six types of iterators. Theseiterator types are expected by generic algorithms, and in order to create aparticular type of iterator yourself it is important to know theircharacteristics. In general, iterators (see alsosection22.14) must define:

The following types of iterators are used when describing genericalgorithms in chapter19: The example given with the RandomAccessIterator illustrates how to relateiterators and generic algorithms: look for the iterator that's required by the(generic) algorithm, and then see whether the datastructure supports therequired type of iterator. If not, the algorithm cannot be used with theparticular datastructure.

18.2.1: std::distance and std::size

Earlier, in section18.2 it was stated that iterators supportpointer arithmetic for containers storing their elements consecutively inmemory. This is not completely true: to determine the number of elementsbetween the elements to which two iterators refer the iterator must support the subtraction operator.

Using pointer arithmetic to compute the number of elements between twoiterators in, e.g., astd::list orstd::unordered_map is not possible,as these containers do not store their elements consecutively in memory.

The functionstd::distance fills in that little gap:std::distance expects two InputIterators and returns the number ofelements between them.

Before usingdistance the<iterator> header file must be included.

If the iterator specified as first argument exceeds the iterator specified asits second argument then the number of elements is non-positive, otherwise itis non-negative. If the number of elements cannot be determined (e.g., theiterators do not refer to elements in the same container), thendistance'sreturn value is undefined.

Example:

    #include <iostream>    #include <unordered_map>            using namespace std;        int main()    {        unordered_map<int, int> myMap = {{1, 2}, {3, 5}, {-8, 12}};            cout << distance(++myMap.begin(), myMap.end()) << '\n'; // shows: 2    }

Theiterator header file also defines the functionstd::size,returning the number of elements in a containers (as returned by thecontainer'ssize member) or of an array whose dimension is known to thecompiler at the point ofstd::size's call. E.g., if the size of an arraydata is known to the compiler, then to call a functionhandler(expecting the address of the first element of an array and the address of thelocation just beyond that array) the following statement can be used:

    handler(data, data + std::size(data));

As noted, thestd::size function is defined in theiteratorheader. However, it's also guaranteed available when including the header fileof a container supporting iterators (including thestring header file).

18.2.2: Insert iterators

Generic algorithms often require a target container into which the results ofthe algorithm are deposited. For example, thecopy genericalgorithm has three parameters. The first two define the range of visitedelements, the third defines the first position where the resultsof the copy operation should be stored.

With thecopy algorithm the number of elements to copy is usuallyavailable beforehand, since that number can usually be provided by pointerarithmetic. However, situations exist where pointer arithmetic cannot beused. Analogously, the number of resulting elements sometimes differs from thenumber of elements in the initial range. The generic algorithmunique_copy is a case in point. Here the number ofelements that are copied to the destination container is normally not knownbeforehand.

In situations like these aninserter adapterfunction can often be used to create elements in the destination container.There are three types of inserter adapters:

The inserter adapters require the existence of two types: Concentrating onback_inserter, this iterator expects the name of acontainer supporting a memberpush_back. The inserter'soperator()member calls the container'spush_back member. Objects of any class supporting apush_backmember can be passed as arguments toback_inserter provided the class adds
    using const_reference = DataType const &;

to its interface (whereDataType const & is the type of the parameterof the class's memberpush_back). Example:

    #include <iostream>    #include <algorithm>    #include <iterator>    using namespace std;    class Insertable    {        public:            using value_type = int;            using const_reference = int const &;            void push_back(int const &)            {}    };    int main()    {        int arr[] = {1};        Insertable insertable;        copy(arr, arr + 1, back_inserter(insertable));    }

18.2.3: Iterators for `istream' objects

Theistream_iterator<Type> can be used to define a setof iterators foristream objects. The general form of theistream_iterator iterator is:
    istream_iterator<Type> identifier(istream &in)

Here,Type is the type of the data elements read from theistreamstream. It is used as the `begin' iterator in an iterator range.Type maybe any type for whichoperator>> is defined in combination withistreamobjects.

The default constructor is used as the end-iterator and corresponds to theend-of-stream. For example,

    istream_iterator<string> endOfStream;

Thestream object that was specified when defining thebegin-iterator isnot mentioned with the default constructor.

Usingback_inserter andistream_iterator adapters, all stringsfrom a stream can easily be stored in a container. Example (usinganonymousistream_iterator adapters):

    #include <iostream>    #include <iterator>    #include <string>    #include <vector>    #include <algorithm>    using namespace std;    int main()    {        vector<string> vs;        copy(istream_iterator<string>(cin), istream_iterator<string>(),             back_inserter(vs));        for        (            vector<string>::const_iterator begin = vs.begin(), end = vs.end();                begin != end; ++begin        )            cout << *begin << ' ';        cout << '\n';    }

18.2.3.1: Iterators for `istreambuf' objects

Input iterators are also available forstreambuf objects.

To read fromstreambuf objects supporting input operationsistreambuf_iterators can be used, supporting theoperations that are also available foristream_iterator. Different fromthe latter iterator typeistreambuf_iterators support three constructors:

In section18.2.4.1 an example is given using bothistreambuf_iterators andostreambuf_iterators.

18.2.4: Iterators for `ostream' objects

Anostream_iterator<Type> adapter can be used to passanostream to algorithms expecting an OutputIterator. Two constructors areavailable for definingostream_iterators:
    ostream_iterator<Type> identifier(ostream &outStream);    ostream_iterator<Type> identifier(ostream &outStream, char const *delim);

Type is the type of the data elements that should be inserted into anostream. It may be any type for whichoperator<< is defined incombination withostream objects. The latter constructor can be used toseparate the individualType data elements bydelimiter strings. Theformer constructor does not use any delimiters.

The example shows howistream_iterators and anostream_iterator may be used to copy information of a file to anotherfile. A subtlety here is that you probably want to usein.unsetf(ios::skipws). It is used to clear theios::skipws flag. As a consequence whitespace characters aresimply returned by the operator, and the file is copied character bycharacter. Here is the program:

    #include <iostream>    #include <algorithm>    #include <iterator>    using namespace std;    int main()    {        cin.unsetf(ios::skipws);        copy(istream_iterator<char>(cin), istream_iterator<char>(),             ostream_iterator<char>(cout));    }

18.2.4.1: Iterators for `ostreambuf' objects

Output iterators are also available forstreambuf objects.

To write tostreambuf objects supporting output operationsostreambuf_iterators can be used, supporting theoperations that are also available forostream_iterator.Ostreambuf_iterators support two constructors:

The next example illustrates the use of bothistreambuf_iterators andostreambuf_iteratorswhen copying a stream in yet another way. Since the stream'sstreambufsare directly accessed the streams and stream flags are bypassed. Consequentlythere is no need to clearios::skipws as in the previous section, whilethe next program's efficiency probably also exceeds the efficiency of theprogram shown in the previous section.
    #include <iostream>    #include <algorithm>    #include <iterator>    using namespace std;    int main()    {        istreambuf_iterator<char> in(cin.rdbuf());        istreambuf_iterator<char> eof;        ostreambuf_iterator<char> out(cout.rdbuf());        copy(in, eof, out);    }

18.2.5: Moving elements to another container

Sometimes we want to move elements from one container to another one. Insteadof retrieving elements in sequence and then moving them into the destinationcontainer (e.g., when extracting words from streams) or explicitly callingstd::move on the source container's elements it's also possible to usemake_move_iterator(std::make_move_iterator(iterator iter)). The function'sargument is an iterator referring to a movable element, and the range ofelements to move from one container to another can be specified by callingtwomake_move_iterators. One receives the source's begin iterator, andother one the source's end iterator. The example illustrates how wordscan be moved into astd::vector<std::string>:
    #include <iostream>    #include <algorithm>    #include <vector>    #include <string>    #include <iterator>    using namespace std;    size_t fstNonEmpty(vector<string> const &vs)    {        return            find_if(vs.begin(), vs.end(),                    [&](string const &str)                    {                        return str != "";                    }            ) - vs.begin();    }    int main()    {        vector<string> vs;        copy(istream_iterator<string>(cin), istream_iterator<string>(),                back_inserter(vs));        cout << "vs contains " << vs.size() << " words\n"                "first non-empty word at index " << fstNonEmpty(vs) << "\n"                "moving the first half into vector v2\n";        vector<string> v2{ make_move_iterator(vs.begin()),                           make_move_iterator(vs.begin() + vs.size() / 2) };        cout << "vs contains " << vs.size() << " words\n"                "first non-empty word at index " << fstNonEmpty(vs) << "\n"                "v2 contains " << v2.size() << " words\n";    }
Note that the elements in the source container have merely moved into thedestination container: the elements in the source container are empty aftermoving. When moving other types of objects the results may be different,although in all cases the source objects should keep their valid states.

18.3: The class 'unique_ptr'

Before using theunique_ptr class presented in this section the<memory> header file must be included.

When pointers are used to access dynamically allocated memory strictbookkeeping is required to prevent memory leaks. When a pointer variablereferring to dynamically allocated memory goes out of scope, the dynamicallyallocated memory becomes inaccessible and the program suffers from amemory leak. Consequently, the programmer has to make sure that the dynamicallyallocated memory is returned to the common pool just before the pointervariable goes out of scope.

When a pointer variable points to a dynamically allocated single value orobject, bookkeeping requirements are greatly simplified when the pointervariable is defined as astd::unique_ptr object.

Unique_ptrs areobjects masquerading as pointers. Since they are objects,their destructors are called when they go out of scope. Their destructorsautomatically delete the dynamically allocated memory to which theypoint. Unique_ptrs (and their cousins shared_ptrs (cf. section18.4)are also calledsmart pointers).

Unique_ptrs have several special characteristics:

The classunique_ptr offers several member functions to access thepointer itself or to have aunique_ptr point to another block ofmemory. These member functions (andunique_ptr constructors) areintroduced in the next few sections.

Unique_ptr can also be used with containers and (generic) algorithms. Theycan properly destruct any type of object, as their constructors acceptcustomizable deleters. In addition, arrays can be handled byunique_ptrs.

18.3.1: Defining `unique_ptr' objects

There are three ways to defineunique_ptrobjects. Each definition contains the usual<type> specifier betweenangle brackets:

18.3.2: Creating a plain `unique_ptr'

Unique_ptr's default constructor defines aunique_ptr not pointing to a particular block of memory:
    unique_ptr<type> identifier;

The pointer controlled by theunique_ptrobject is initialized to0 (zero). Although theunique_ptr objectitself is not the pointer, its valuecan be compared to0. Example:

    unique_ptr<int> ip;    if (!ip)        cout << "0-pointer with a unique_ptr object\n";

Alternatively, the memberget can be used (cf. section18.3.5).

18.3.3: Moving another `unique_ptr'

Aunique_ptr may be initializedusing an rvalue reference to aunique_ptr object for the same type:
    unique_ptr<type> identifier(other unique_ptr object);

The move constructor is used, e.g., in the following example:

    void mover(unique_ptr<string> &&param)    {        unique_ptr<string> tmp(move(param));    }

Analogously, the assignment operator can beused. Aunique_ptr object may be assigned to a temporaryunique_ptrobject of the same type (again move-semantics is used). For example:

    #include <iostream>    #include <memory>    #include <string>    using namespace std;    int main()    {        unique_ptr<string> hello1{ new string{ "Hello world" } };        unique_ptr<string> hello2{ move(hello1) };        unique_ptr<string> hello3;        hello3 = move(hello2);        cout << // *hello1 << '\n' <<   // would have segfaulted                // *hello2 << '\n' <<   // same                *hello3 << '\n';    }    // Displays:    Hello world
The example illustrates that Ifhello1 orhello2 had been inserted intocout asegmentation fault would have resulted. The reason for this should now beclear: it is caused by dereferencing 0-pointers. In the end, onlyhello3actually points to the originally allocatedstring.

18.3.4: Pointing to a newly allocated object

Aunique_ptr is most often initializedusing a pointer to dynamically allocated memory. The generic form is:
    unique_ptr<type [, deleter_type]> identifier(new-expression            [, deleter = deleter_type{}]);

The second (template) argument (deleter(_type)) is optional and may refer to a free function, a function object handling thedestruction of the allocated memory, or a lambda function. A deleter is used,e.g., in situations where a double pointer is allocated and the destructionmust visit each nested pointer to destroy the allocated memory (see below foran illustration).

Here is an example initializing aunique_ptr pointing to astringobject:

    unique_ptr<string> strPtr{ new string{ "Hello world" } };

The argument that is passed to the constructor is the pointer returned byoperator new. Note thattype doesnot mention the pointer. The type that is used in theunique_ptr constructionis the same as the type that is used innew expressions.

Here is an example showing how an explicitly defined deleter may be used todelete a dynamically allocated array of pointers to strings:

    #include <string>    #include <memory>    using namespace std;    struct Deleter    {        size_t d_size;        Deleter(size_t size = 0)        :            d_size(size)        {}        void operator()(string **ptr) const        {            for (size_t idx = 0; idx < d_size; ++idx)                delete ptr[idx];            delete[] ptr;        }    };    int main()    {        unique_ptr<string *, Deleter> sp2{ new string *[10](),                                           Deleter{ 10 } };                                        // for illustration purposes:                                        // retrieving the Deleter        [[maybe_unused]] Deleter &obj = sp2.get_deleter();    }

Aunique_ptr can be used to reach the member functions that are available forobjects allocated by thenew expression. These members can be reached asif theunique_ptr was a plain pointer to the dynamically allocatedobject. For example, in the following program the text `C++' is insertedbehind the word `hello':

    #include <iostream>    #include <memory>    #include <cstring>    using namespace std;    int main()    {        unique_ptr<string> sp{ new string{ "Hello world" } };        cout << *sp << '\n';        sp->insert(strlen("Hello "), "C++ ");        cout << *sp << '\n';    }    /*        Displays:            Hello world            Hello C++ world    */

18.3.5: Operators and members

The classunique_ptr offers the following operators:

The classunique_ptr supports the followingmember functions:

18.3.6: Using `unique_ptr' objects for arrays

When aunique_ptr is used to store arrays the dereferencing operator makeslittle sense but with arraysunique_ptr objects benefit from indexoperators. The distinction between a single objectunique_ptr and aunique_ptr referring to a dynamically allocated array of objects isrealized through a template specialization.

With dynamically allocated arrays the following syntax is available:

In these cases the smart pointer's destructors calldelete[] rather thandelete.

18.4: The class `shared_ptr'

In addition to the classunique_ptr the classstd::shared_ptr<Type> is available, which is a referencecounting smart pointer.

Before usingshared_ptrs the<memory> header file must be included.

The shared pointer automatically destroys its content once its referencecount has decayed to zero. As withunique_ptr, when defining ashared_ptr<Base> to store a newly allocatedDerived class object, thereturnedBase * may be cast to aDerived * using astatic_cast:polymorphism isn't required, and when resetting theshared_ptr or when theshared_ptr goes out of scope, no slicing occurs, andDerived'sdestructor (or, if configured: deleter) is called (cf. section18.3).

Shared_ptrs support copy and move constructors as well as standard andmove overloaded assignment operators.

Likeunique_ptrs, shared_ptrs may refer to dynamically allocated arrays.

18.4.1: Defining `shared_ptr' objects

There are four ways to defineshared_ptrobjects. Each definition contains the usual<type> specifier betweenangle brackets:

18.4.2: Creating a plain `shared_ptr'

Shared_ptr's default constructor defines ashared_ptr not pointing to a particular block of memory:
    shared_ptr<type> identifier;

The pointer controlled by theshared_ptrobject is initialized to0 (zero). Although theshared_ptr objectitself is not the pointer, its valuecan be compared to0. Example:

    shared_ptr<int> ip;    if (!ip)        cout << "0-pointer with a shared_ptr object\n";

Alternatively, the memberget can be used (cf. section18.4.4).

18.4.3: Pointing to a newly allocated object

Most often ashared_ptr is initialized by adynamically allocated block of memory. The generic form is:
    shared_ptr<type> identifier(new-expression [, deleter]);

The second argument (deleter) is optional and refers to a function object or free function handling thedestruction of the allocated memory. A deleter is used, e.g., in situationswhere a double pointer is allocated and the destruction must visit each nestedpointer to destroy the allocated memory (see below for an illustration). Itis used in situations comparable to those encountered withunique_ptr(cf. section18.3.4).

Here is an example initializing ashared_ptr pointing to astringobject:

    shared_ptr<string> strPtr{ new string{ "Hello world" } };

The argument that is passed to the constructor is the pointer returned byoperator new. Note thattype doesnot mention the pointer. The type that is used in theshared_ptr constructionis the same as the type that is used innew expressions.

The next example illustrates that twoshared_ptrs indeed share theirinformation. After modifying the information controlled by one of theobjects the information controlled by the other object is modified as well:

    #include <iostream>    #include <memory>    #include <cstring>    using namespace std;    int main()    {        shared_ptr<string> sp(new string{ "Hello world" });        shared_ptr<string> sp2(sp);        sp->insert(strlen("Hello "), "C++ ");        cout << *sp << '\n' <<                *sp2 << '\n';    }    /*        Displays:            Hello C++ world            Hello C++ world    */

18.4.4: Operators and members

The classshared_ptr offers the followingoperators:

The followingmember function member functions are supported:

18.4.5: Casting shared pointers

Be cautious when using standardC++ style casts in combination withshared_ptr objects. Consider the following two classes:
    struct Base    {};    struct Derived: public Base    {};

As withunique_ptr, when defining ashared_ptr<Base> to store anewly allocatedDerived class object, the returnedBase * may be castto aDerived * using astatic_cast: polymorphism isn't required, andwhen resetting theshared_ptr or when theshared_ptr goes out ofscope, no slicing occurs, andDerived's destructor is called (cf. section18.3).

Of course, ashared_ptr<Derived> can easily be defined. Since aDerived object is also aBase object, a pointer toDerived canbe considered a pointer toBase without using casts, but astatic_castcould be used to force the interpretation of aDerived * to aBase *:

    Derived d;    static_cast<Base *>(&d);

However, a plainstatic_cast cannot be used when initializing a sharedpointer to aBase using theget member of a shared pointer to aDerived object. The following code snipped eventually results in anattempt to delete the dynamically allocatedBase object twice:

    shared_ptr<Derived> sd{ new Derived };    shared_ptr<Base> sb{ static_cast<Base *>(sd.get()) };

Sincesd andsb point at the same object~Base will be calledfor the same object whensb goes out of scope and whensd goes out ofscope, resulting in premature termination of the program due to adouble free error.

These errors can be prevented using casts that were specifically designedfor being used withshared_ptrs. These casts use specialized constructorsthat create ashared_ptr pointing to memory but shares ownership (i.e.,a reference count) with an existingshared_ptr. These special casts are:

18.4.6: Using `shared_ptr' objects for arrays

Theshared_ptr class can also be used to handle dynamically allocatedarrays of objects. To use it on arrays simply use the square brackets whenspecifying theshared_ptr's type. Here is an example:
    struct Type    {        ~Type()        {            cout << "destr\n";  // show the object's destruction        }    };    int main()    {        shared_ptr<Type[]> sp{ new Type[3] };        sp[0] = sp[1];    }

When theshared_ptr itself is initialized with an array of pointers, thenadeleter must be used to delete the memory the pointers point at. In thatcase the deleter is responsible for returning the memory to the common pool,usingdelete[] when eventually deleting the array of pointers:

    struct Type    {        int value = 0;        ~Type()        {            cout << "destr " << this << '\n';        }    };    template<typename Tp>    struct Deleter    {        size_t d_size;        Deleter(size_t size)        :            d_size(size)        {}        void operator()(Tp **ptr)        {            for (; d_size--; )                delete ptr[d_size];            delete[] ptr;           // <-- NOTE !        }    };    int main()    {        shared_ptr<Type *[]> sp{ new Type *[2] {new Type, new Type},                                 Deleter<Type>{ 2 } };    }

18.5: Smart `smart pointer' construction: `make_shared' and `make_unique'

Usually ashared_ptr is initialized at definition timewith a pointer to a newly allocated object. Here is an example:
    std::shared_ptr<string> sptr{ new std::string{ "hello world" } }

In such statementstwo memory allocation calls are used: one for theallocation of thestd::string and one used interally bystd::shared_ptr's constructor itself.

The two allocations can be combined into one single allocation (which isalso slightly more efficient than explicitly callingshared_ptr'sconstructor) using themake_shared template. The function templatestd::make_shared has the following prototype:

    template<typename Type, typename ...Args>    std::shared_ptr<Type> std::make_shared(Args ...args);

Before usingmake_shared the<memory> header file must be included.

This function template allocates an object of typeType, passingargs to its constructor (usingperfect forwarding, see section22.5.2), and returns ashared_ptr initialized with the address ofthe newly allocatedType object.

Here is how the abovesptr object can be initializedusingstd::make_shared. Notice the use ofauto which frees us fromhaving to specifysptr's type explicitly:

    auto sptr(std::make_shared<std::string>("hello world"));

After this initializationstd::shared_ptr<std::string> sptr has beendefined and initialized. It could be used as follows:

    std::cout << *sptr << '\n';

In addition tomake_shared the functionstd::make_unique can be used. It can be usedmake_shared but returns astd::unique_ptr rather than ashared_ptr.

18.6: Classes having pointer data members

Classes having pointer data members require specialattention. In particular at construction time one must be careful to prevent wild pointers and/or memory leaks. Considerthe following class defining two pointer data members:
    class Filter    {        istream *d_in;        ostream *d_out;        public:            Filter(char const *in, char const *out);    };

Assume thatFilter objects filter information read from*d_in andwrite the filtered information to*d_out. Using pointers to streamsallows us to have them point at any kind of stream likeistreams,ifstreams, fstreams oristringstreams. The shown constructor could beimplemented like this:

    Filter::Filter(char const *in, char const *out)    :        d_in(new ifstream{ in }),        d_out(new ofstream{ out })    {        if (!*d_in || !*d_out)            throw "Input and/or output stream not available"s;    }

Of course, the construction could fail.new could throw an exception;the stream constructors could throw exceptions; or the streams could not beopened in which case an exception is thrown from the constructor's body. Usinga function try block helps. Note that ifd_in's initialization throws,there's nothing to be worried about. TheFilter object hasn't beenconstructed, its destructor is not called and processing continues at thepoint where the thrown exception is caught. ButFilter's destructor isalso not called whend_out's initialization or the constructor'sifstatement throws: no object, and hence no destructor is called. This mayresult in memory leaks, asdelete isn't called ford_in and/ord_out. To prevent this,d_in andd_out must first be initializedto 0 and only then the initialization can be performed:

    Filter::Filter(char const *in, char const *out)    try    :        d_in(0),        d_out(0)    {        d_in = new ifstream{ in };        d_out = new ofstream{ out };        if (!*d_in || !*d_out)            throw "Input and/or output stream not available"s;    }    catch (...)    {        delete d_out;        delete d_in;    }

This quickly gets complicated, though. IfFilter harbors yet anotherdata member of a class whose constructor needs two streams then that datacannot be constructed or it must itself be converted into a pointer:

    Filter::Filter(char const *in, char const *out)    try    :        d_in(0),        d_out(0)        d_filterImp(*d_in, *d_out)    // won't work    { ... }    // instead:    Filter::Filter(char const *in, char const *out)    try    :        d_in(0),        d_out(0),        d_filterImp(0)    {        d_in = new ifstream(in);        d_out = new ofstream(out);        d_filterImp = new FilterImp(*d_in, *d_out);        ...    }    catch (...)    {        delete d_filterImp;        delete d_out;        delete d_in;    }

Although the latter alternative works, it quickly gets hairy. Insituations like these smart pointers should be used to prevent thehairiness. By defining the stream pointers as (smart pointer) objects theywill, once constructed, properly be destroyed even if the rest of theconstructor's code throws exceptions. Using aFilterImp and twounique_ptr data membersFilter's setup and its constructor becomes:

    class Filter    {        std::unique_ptr<std::ifstream> d_in;        std::unique_ptr<std::ofstream> d_out;        FilterImp d_filterImp;        ...    };    Filter::Filter(char const *in, char const *out)    try    :        d_in(new ifstream(in)),        d_out(new ofstream(out)),        d_filterImp(*d_in, *d_out)    {        if (!*d_in || !*d_out)            throw "Input and/or output stream not available"s;    }

We're back at the original implementation but this time without having toworry about wild pointers and memory leaks. If one of the member initializersthrows the destructors of previously constructed data members (which are nowobjects) are always called.

As arule of thumb: when classes need to define pointer data membersthey should define those pointer data members as smart pointers if there's anychance that their constructors throw exceptions.

18.7: Comparison classes

With the introduction of thespaceship operator (<=>, cf. section11.7.2) severalcomparison category classes were added to the standard namespace.

Comparison classes are required when implementing the spaceship operator, andto use them (or when declaring and implementing the spachip operator) the<compare> header file must be included.

The weak class types do not supportsubstitutability. Substitutabilitymeans that if two objectsone andtwo are equal (so:one == two istrue), thenfun(one) == fun(two) is alsotrue. Herefun is anyfunction that only uses public const members of its argument that returnvalue types (as compared to pointer types) which also support comparisons(also calledcomparison-salient state).

The operators of comparison classes expect at least one argument of their ownclass types. The other argument may either also be of their own class types orit can be the value 0 (or any value that can be considered 0, likenullptr_t).

There are five comparison classes, primarily used when implementingspaceship operators:

18.7.1: The class `weak_equality'

The classstd::weak_equality is used when implementingthe spaceship operator for classes that only support (in)equality comparisons,but not substitutability. The class provides free functionsoperator== andoperator!= expectingweak_equality arguments (oneargument may be 0) and it defines two static objects:

Note: at the current release of the GnuC++ compiler (10.0.0) this classis not yet available in the<compare> header file.

18.7.2: The class `strong_equality'

The classstd::strong_equality is used when implementingthe spaceship operator for classes that only support (in)equality comparisonsas well as substitutability. The class provides free functionsoperator== andoperator!= expectingstrong_equality arguments (oneargument may be 0) and it defines four static objects:

Note: at the current release of the GnuC++ compiler (10.0.0) this classis not yet available in the<compare> header file.

18.7.3: The class `partial_ordering'

The classstd::partial_ordering is used whenimplementing the spaceship operator for classes that support all comparisonoperators (where one operand may be zero), that do not supportsubstitutability, and whose objects, using the spaceship operator itself, canalso be compared to any other type of object.

The classpartial_ordering provides free functions for all comparisonoperations (==, !=, <, <=, >, and>=) expectingpartial_orderingarguments (one argument may be 0). It also defines four static objects whichcan be returned by the spaceship operator:

As an example, consider road taxes. Trucks, cars, and motor cycles have to payroad taxes, but there's no road tax for bicycles. For ordering road taxes aclassRoadTax may be used, defining the following spaceship operator(assuming that all types of vehicles are derived from a classVehicle, andthatVehicle has a (virtual) memberdouble roadTax() returning theamount of road tax that is due for the various types of vehicles; the amountis negative if no road tax is required):

    partial_ordering RoadTax::operator<=>(Vehicle const &lhs,                                           Vehicle const &rhs)    {        return          lhs.roadTax() < 0 or rhs.roadTax() < 0 ?                                          partial_ordering::unordered :          lhs.roadTax() < rhs.roadTax() ? partial_ordering::less      :          lhs.roadTax() > rhs.roadTax() ? partial_ordering::greater   :                                          partial_ordering::equivalent;    }

18.7.4: The class `weak_ordering'

The classstd::weak_ordering is used whenimplementing the spaceship operator for classes that support all comparisonoperators (where one operand may be zero), and that do not supportsubstitutability.

The classweak_ordering differs from thepartial_ordering class inthatunordered cannot be used as a comparison result. Like the classpartial_ordering it provides free functions for all comparisonoperations (==, !=, <, <=, >, and>=) expectingpartial_orderingarguments (one argument may be 0). It also defines three static objects whichcan be returned by the spaceship operator:

The example in the previous section can easily adapted to theweak_ordering comparison class: if theroadTax members of vehicles forwhich no road tax is due return zero thenRoadTax's spaceship operator canbe implemented this way:

    weak_ordering RoadTax::operator<=>(Vehicle const &lhs,                                           Vehicle const &rhs)    {        return          lhs.roadTax() < rhs.roadTax() ? weak_ordering::less      :          lhs.roadTax() > rhs.roadTax() ? weak_ordering::greater   :                                          weak_ordering::equal;    }

18.7.5: The class `strong_ordering'

The classstd::strong_ordering is used whenimplementing the spaceship operator for classes that support all comparisonoperators (where one operand may be zero), as well as substitutability.

The classstrong_ordering provides free functions for all comparisonoperations (==, !=, <, <=, >, and>=) expectingpartial_orderingarguments (one argument may be 0). It also defines three static objects whichcan be returned by the spaceship operator:

An example where the classstrong_ordering was used has already beenprovided in section11.7.2, where the spaceship operator itself wasintroduced.

18.8: Regular Expressions

C++ itself provides facilities for handling regular expressions. Regularexpressions were already available inC++ via itsC heritage (asChas always offered functions likeregcomp andregexec), but thededicated regular expression facilities have a richer interface than thetraditionalC facilities, and can be used in code using templates.

Before using the specificC++ implementations of regular expressions theheader file<regex> must be included.

Regular expressions are extensively documented elsewhere (e.g.,regex(7),Friedl, J.E.FMastering Regular Expressions, O'Reilly). The reader is referred to these sources for a refresher on the topic ofregular expressions. In essence, regular expressions define a smallmeta-language recognizing textual units (like `numbers', `identifiers', etc.).They are extensively used in the context oflexical scanners (cf. section26.6.1) when defining the sequence of input characters associated withtokens. But they are also intensively used in other situations. Programslikesed(1) andgrep(1) use regular expressions to find pieces of textin files having certain characteristics, and a program likeperl(1) addssome `sugar' to the regular expression language, simplifying the constructionof regular expressions. However, though extremely useful, it is also wellknown that regular expressions tend to be very hard to read. Some even callthe regular expression language awrite-only language: while specifying aregular expression it's often clear why it's written in a particular way. Butthe opposite, understanding what a regular expression is supposed to representif you lack the proper context, can be extremely difficult. That's why, fromthe onset and as arule of thumb, it is stressed that an appropriatecomment should be provided, witheach regular expression, as to what it issupposed to match.

In the upcoming sections first a short overview of the regular expressionlanguage is provided, which is then followed by the facilitiesC++ iscurrently offering for using regular expressions. These facilities mainlyconsist of classes helping you to specify regular expression, matching them totext, and determining which parts of the text (if any) match (parts of) thetext being analyzed.

18.8.1: The regular expression mini language

Regular expressions are expressions consisting of elements resembling those ofnumeric expressions. Regular expressions consist of basic elements andoperators, having various priorities and associations. Like numericexpressions, parentheses can be used to group elements together to form aunit on which operators operate. For an extensive discussion the reader isreferred to, e.g., section 15.10 of theecma-international.org page, whichdescribes the characteristics of the regular expressions used by default byC++'sregex classes.

C++'s default definition of regular expressions distinguishes thefollowingatoms:

In addition to these basic atoms, the following special atoms are available(which can also be used in character classes):

Atoms may be concatenated. Ifr ands are atoms then the regularexpressionrs matches a target text if the target text matchesrands, in that order (without any intermediate characters inside the target text). E.g., the regular expression[ab][cd] matches thetarget textac, but not the target texta:c.

Atoms may be combined using operators. Operators bind to the precedingatom. If an operator should operate on multiple atoms the atoms must besurrounded by parentheses (as in(r) used above: ifr isone, thenuse(one) when applying the operator to the wordone, instead of justthe finale). To use an operator character as an atom it can beescaped. Eg.,* represents an operator,\* the atom characterstar. Note that character classes do not recognize escape sequences:[\*]represents a character class consisting of two characters: a backslash and astar.

The following operators are supported (r ands represent regularexpression atoms):

When a regular expression contains marked sub-expressions and multipliers, andthe marked sub-expressions are multiply matched, then the target's finalsub-string matching the marked sub-expression is reported as the text matchingthe marked sub-expression. E.g, when usingregex_search (cf. section18.8.4.3), marked sub-expression (((a|b)+\s?)), and target texta ab, thena a b is the fully matched text, whileb is reported as thesub-string matching the first and second marked sub-expressions.

18.8.1.1: Character classes

Inside a character class all regular expression operators lose their specialmeanings, except for the special atoms\s, \S, \d, \D, \w, and\W; thecharacter range operator-; the end of character class operator];and, at the beginning of the character class,^. Except in combinationwith the special atoms the escape character is interpreted as a literalbackslash character (to define a character class containing a backslash and ad simply use[d\]).

To add a closing bracket to a character class use[] immediately followingthe initial open-bracket, or start with[^] for a negated character classnot containing the closing bracket. Minus characters are used to definecharacter ranges (e.g.,[a-d], defining[abcd]) (be advised that theactual range may depend on the locale being used). To add a literal minuscharacter to a character class put it at the very beginning ([-, or[^-) or at the very end (-]) of a character class.

Once a character class has started, all subsequent characters are added to theclass's set of characters, until the final closing bracket (]) has beenreached.

In addition to characters and ranges of characters, character classes may alsocontainpredefined sets of character. They are:

         [:alnum:] [:alpha:] [:blank:]         [:cntrl:] [:digit:] [:graph:]         [:lower:] [:print:] [:punct:]         [:space:] [:upper:] [:xdigit:]

These predefined sets designate sets of characters equivalent to thecorresponding standardCisXXX function. For example,[:alnum:]defines all characters for whichisalnum(3) returns true.

18.8.2: Defining regular expressions: std::regex

Before using the(w)regex class presented in this section the<regex> header file must be included.

The typesstd::regex andstd::wregex define regularexpression patterns. They define, respectively the typesbasic_regex<char> andbasic_regex<wchar_t>types. Below, the classregex is used, but in the exampleswregexcould also have been used.

Regular expression facilities were, to a large extent, implemented throughtemplates, using, e.g., thebasic_string<char> type (which is equal tostd::string). Likewise, generic types likeOutputIter (outputiterator) andBidirConstIter (bidirectional const iterator) are used withseveral functions. Such functions are function templates. Function templatesdetermine the actual types from the arguments that are provided atcall-time.

These are the steps that are commonly taken when using regular expressions:

The wayregex objects handle regular expressions can be configured using abit_or combined set ofstd::regex_constants values,defining aregex::flag_type value. Theseregex_constants are:

Constructors

The default, move and copy constructors are available. Actually, thedefault constructor defines one parameter of typeregex::flag_type, forwhich the valueregex_constants::ECMAScript is used by default.

Member functions

18.8.3: Retrieving matches: std::match_results

Once aregex object is available, it can be used to match some target textagainst the regular expression. To match a target text against a regularexpression the following functions, described in the next section(18.8.4), are available:

These functions must be provided with a target text and aregex object(which is not modified by these functions). Usually another argument, astd::match_results object is also passed to thesefunctions, to contain the results of the regular expression matchingprocedure.

Before using thematch_results class the<regex> header file must beincluded.

Examples of usingmatch_results objects are provided in section18.8.4. This and the next section are primarily for referentialpurposes.

Various specializations of the classmatch_results exist. Thespecialization that is used should match the specializations of the usedregex class. E.g., if the regular expression was specified as acharconst * thematch_results specialization should also operate oncharconst * values. The various specializations ofmatch_results have beengiven names that can easily be remembered, so selecting the appropriatespecialization is simple.

The classmatch_results has the following specializations:

Constructors

The default, copy, and move constructors are available. The defaultconstructor defines anAllocator const & parameter, which by default isinitialized to the default allocator. Normally, objects of the classmatch_results receive their match-related information by passing them tothe above-mentioned functions, likeregex_match. When returning from thesefunctions members of the classmatch_results can be used to retrievespecific results of the matching process.

Member functions

18.8.4: Regular expression matching functions

Before using the functions presented in this section the<regex> headerfile must be included.

There are three major families of functions that can be used to match a targettext against a regular expression. Each of these functions, as well as thematch_results::format member, has a finalstd::regex_constants::match_flag_type parameter (see the next section),which is given the default valueregex_constants::match_default which canbe used to fine-tune the way the regular expression and the matching processis being used. Thisfinal parameter is not explicitly mentioned with the regular expressionmatching functions or with theformat member. The three families offunctions are:

Thematch_results::format member can be used afterregex_replaceand is discussed after coveringregex_replace (section18.8.4.4).

18.8.4.1: The std::regex_constants::match_flag_type flags

All overloadedformat members and all regular expression matchingfunctions accept a finalregex_constants::match_flag_type argument, which is abit-masked type, for which thebit_or operator can be used. Allformatmembers by default specify the argumentmatch_default.

Thematch_flag_type enumeration defines the following values (below,`[first, last)' refers to the character sequence being matched).

18.8.4.2: Matching full texts: std::regex_match

The regular expression matching functionstd::regex_matchreturnstrue if the regular expression defined in its providedregex argumentfully matches the provided target text. This means thatmatch_results::prefix andmatch_results::suffix must return emptystrings. But defining sub-expressions is OK.

The following overloaded variants of this function are available:

Here is a small example: the regular expression matches the matched text(provided byargv[1]) if it starts with 5 digits and then merely contains letters ([[:alpha:]]). The digits can be retrieved assub-expression 1:
    #include <iostream>    #include <regex>        using namespace std;        int main(int argc, char const **argv)    {        regex re("(\\d{5})[[:alpha:]]+");         cmatch results;        if (not regex_match(argv[1], results, re))            cout << "No match\n";        else            cout << "size: " << results.size() << ": " <<                     results.str(1) << " -- " << results.str() << '\n';    }

18.8.4.3: Partially matching text: std::regex_search

Different fromregex_match the regular expression matching functionstd::regex_search returnstrue if the regularexpression defined in itsregex argument partially matches the targettext.

The following overloaded variants of this function are available:

The following example illustrates howregex_search could be used:
     1: #include <iostream>     2: #include <string>     3: #include <regex>     4:      5: using namespace std;     6:      7: int main()     8: {     9:     while (true)    10:     {    11:         cout << "Enter a pattern or plain Enter to stop: ";    12:     13:         string pattern;    14:         if (not getline(cin, pattern) or pattern.empty())    15:             break;    16:     17:         regex re(pattern);    18:         while (true)    19:         {    20:             cout << "Enter a target text for `" << pattern << "'\n"    21:                     "(plain Enter for the next pattern): ";    22:     23:             string text;    24:             if (not getline(cin, text) or text.empty())    25:                 break;    26:     27:             smatch results;    28:             if (not regex_search(text, results, re))    29:                 cout << "No match\n";    30:             else    31:             {    32:                 cout << "Prefix: "  << results.prefix() << "\n"    33:                         "Match:  "  << results.str()    << "\n"    34:                         "Suffix: "  << results.suffix() << "\n";    35:                 for (size_t idx = 1; idx != results.size(); ++idx)    36:                     cout << "Match " << idx << " at offset " <<    37:                                 results.position(idx) << ": " <<    38:                                 results.str(idx) << '\n';    39:             }    40:         }    41:     }    42: }

18.8.4.4: The member std::match_results::format

Thematch_results::format member is a rather complex memberfunction of the classmatch_results, which can be used to modify textwhich was previously matched against a regular expression, e.g., using thefunctionregex_search. Because of its complexity and because thefunctionality of another regular expression processing function(regex_replace) offers similar functionality it is discussed at this pointin theC++ Annotations, just before discussing theregex_replace function.

Theformat member operates on (sub-)matches contained in amatch_results object, using aformat string, and producing text inwhich format specifiers (like$&) are replaced bymatching sections of the originally provided target text. In addition, theformat member recognizes all standardC escape sequences (like\n). Theformat member is used to create text that is modified withrespect to the original target text.

As a preliminary illustration: ifresults is amatch_results objectandmatch[0] (the fully matched text) equals `hello world', thencallingformat with the format stringthis is [$&] produces the textthis is [hello world]. Note the specification$& in this formatstring: this is an example of a format specifier. Here is an overview of allsupported format specifiers:

Four overloaded versions of theformat members are available. Alloverloaded versions define a finalregex_constants::match_flag_typeparameter, which is by default initialized tomatch_default. This finalparameter is not explicitly mentioned in the following coverage of theformat members.

To further illustrate the way theformat members can be used it is assumedthat the following code has been executed:

     1:     regex re("([[:alpha:]]+)\\s+(\\d+)");  // letters blanks digits     2:      3:     smatch results;     4:     string target("this value 1024 is interesting");     5:      6:     if (not regex_search(target, results, re))     7:         return 1;
After callingregex_search (line 6) the results of the regularexpression matching process are available in thematch_results resultsobject that is defined in line 3.

The first two overloadedformat functions expect an output-iterator towhere the formatted text is written. These overloaded members return the final output iterator, pointing just beyond the character that was lastwritten.

The remaining two overloadedformat members expect astd::string oran NTBS defining the format string. Both members return astd::stringcontaining the formatted text:

The next example shows how astring can be obtainedin which the order of the first and second marked sub-expressions contained inthe previously obtainedmatch_results object have beenswapped:
    string reverse(results.format("$2 and $1"));

18.8.4.5: Modifying target strings: std::regex_replace

The family ofstd::regex_replace functions uses regularexpressions to perform substitution on sequences of characters. Theirfunctionality closely resembles the functionality of thematch_results::format member discussed in the previous section. Thefollowing overloaded variants are available:

18.9: Randomization and Statistical Distributions

Before the statistical distributions and accompanying random numbergenerators can be used the<random> header file must be included.

The STL offers several standard mathematical (statistical)distributions. These distributions allow programmers to obtain randomlyselected values from a selected distribution.

These statistical distributions need to be provided with a random numbergenerating object. Several of such random number generating objects areprovided, extending the traditionalrand function that is part of theC standard library.

These random number generating objects produce pseudo-random numbers, whichare then processed by the statistical distribution to obtain values that arerandomly selected from the specified distribution.

Although the STL offers various statistical distributions their functionalityis fairly limited. The distributions allow us to obtain a random number fromthese distributions, butprobability density functions orcumulative distribution functions are currently not provided by the STL. These functions (distributions as wellas the density and the cumulative distribution functions) are, however,available in other libraries, like theboost math library (specifically:
http://www.boost.org/doc/libs/1_44_0/libs/math/doc/sf_and_dist/html/index.html).

It is beyond the scope of theC++ Annotations to discuss the mathematicalcharacteristics of the various statistical distributions. The interestedreader is referred to the pertinent mathematical textbooks (like Stuart andOrd's (2009)Kendall's Advanced Theory of Statistics, Wiley) or to web-locationslikehttp://en.wikipedia.org/wiki/Bernoulli_distribution.

18.9.1: Random Number Generators

The followinggenerators are available:

Class template Integral/Floating point Quality Speed Size of state

linear_congruential_engine Integral Medium Medium 1
subtract_with_carry_engine Both Medium Fast 25
mersenne_twister_engine Integral Good Fast 624

Thelinear_congruential_engine random number generator computes

valuei+1 = (+a * valuei + c+) % m
It expects template arguments for, respectively, the data type to containthe generated random values; the multipliera; the additive constantc; and the modulo valuem. Example:
    linear_congruential_engine<int, 10, 3, 13> lincon;

Thelinear_congruential generator may be seeded by providing itsconstructor with a seeding-argument. E.g.,lincon(time(0)).

Thesubtract_with_carry_engine random number generator computes

valuei = (valuei-s - valuei-r - carryi-1) % m
It expects template arguments for, respectively, the data type to containthe generated random values; the modulo valuem; and the subtractiveconstantss andr. Example:
    subtract_with_carry_engine<int, 13, 3, 13> subcar;

Thesubtract_with_carry_engine generator may be seeded by providingits constructor with a seeding-argument. E.g.,subcar(time(0)).

The predefinedmersenne_twister_engine mt19937 (predefined as type in the<random> header file) is used in the examples below. It can be constructedusing`mt19937 mt' or it can be seeded by providing itsconstructor with an argument (e.g.,mt19937 mt(time(0))). Its functioncall operator returns a random unsigned integral value.

Other ways to initialize themersenne_twister_engine are beyond thescope of theC++ Annotations (but see Lewisetal. ( Lewis, P.A.W., Goodman, A.S., and Miller, J.M. (1969), A pseudorandomnumber generator for the System/360, IBM Systems Journal, 8, 136-146.) (1969)).

The random number generators may also be seeded by calling their membersseed acceptingunsigned long values or generator functions (as inlc.seed(time(0)), lc.seed(mt)).

The random number generators offer membersmin andmaxreturning, respectively, their minimum and maximum values (inclusive). If areduced range is required the generators can be nested in a function or classadapting the range.

Here's a small example showing how themersenne_twister_enginemt19937 can be used to generate random numbers:

    #include <iostream>    #include <ctime>    #include <random>    using namespace std;        // arguments: 1st: number of random numbers to generate        //            2nd: lowest positve random number,        //            3rd: highest positive random number    int main(int argc, char **argv)    {        mt19937 mt( time(0) );      // seed with the current time in secs.        for (            size_t nGenerate = stoul(argv[1]), lowest = stoul(argv[2]),                                               mod = stoul(argv[3]) + 1 - lowest;                nGenerate--;        )            cout << (lowest + mt() % mod) << ' ';        cout << '\n';    }

18.9.2: Statistical distributions

In the following sections the various statistical distributions that aresupported byC++ are covered. The notationRNG is used toindicate aRandom Number Generator andURNG is used to indicate aUniform Random Number Generator. With each distribution astruct param_type is defined containing the distribution's parameters. Theorganization of theseparam_type structs depends on (and is describedat) the actual distribution.

All distributions offer the following members (result_type refers tothe type name of the values returned by the distribution):

All distributions support the following operators (distribution-name should be replaced by the name of the intended distribution, e.g.,normal_distribution):

The following example shows how the distributions can be used. Replacingthe name of the distribution (normal_distribution) by anotherdistribution's name is all that is required to switch distributions. Alldistributions have parameters, like the mean and standard deviation of thenormal distribution, and all parameters have default values. The names of theparameters vary over distributions and are mentioned below at the individualdistributions. Distributions offer members returning or setting theirparameters.

Most distributions are defined as class templates, requiring the specificationof a data type that is used for the function's return type. If so, an emptytemplate parameter type specification (<>) will get you the defaulttype. The default types are eitherdouble (for real valued return types)orint (for integral valued return types). The template parameter typespecification must be omitted with distributions that are not defined astemplate classes.

Here is an example showing the use of the statistical distributions, appliedto the normal distribution:

#include <iostream>#include <ctime>#include <random>using namespace std;int main(){    std::mt19937 engine(time(0));    std::normal_distribution<> dist;    for (size_t idx = 0; idx < 10; ++idx)        std::cout << "a random value: " << dist(engine) << "\n";    cout << '\n' <<        dist.min() << " " << dist.max() << '\n';}

18.9.2.1: Bernoulli distribution

Thebernoulli_distribution is used to generate logical truth (boolean)values with a certain probabilityp. It is equal to a binomialdistribution for one experiment (cf18.9.2.2).

The bernoulli distribution isnot defined as a class template.

Defined types:

    using result_type = bool;    struct param_type    {      explicit param_type(double prob = 0.5);      double p() const;                     // returns prob    };

Constructor and members:

18.9.2.2: Binomial distribution

Thebinomial_distribution<IntType = int> is used to determine theprobability of the number of successes in a sequence ofn independentsuccess/failure experiments, each of which yields success with probabilityp.

The template type parameterIntType defines the type of the generatedrandom value, which must be an integral type.

Defined types:

    using result_type =  IntType;    struct param_type    {      explicit param_type(IntType trials, double prob = 0.5);      IntType t() const;                    // returns trials      double p() const;                     // returns prob    };

Constructors and members and example:

18.9.2.3: Cauchy distribution

Thecauchy_distribution<RealType = double> looks similar to a normaldistribution. But cauchy distributions have heavier tails. When studyinghypothesis tests that assume normality, seeing how the tests perform on datafrom a Cauchy distribution is a good indicator of how sensitive the tests areto heavy-tail departures from normality.

The mean and standard deviation of the Cauchy distribution are undefined.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType a = RealType(0),                            RealType b = RealType(1));        double a() const;        double b() const;    };

Constructors and members:

18.9.2.4: Chi-squared distribution

Thechi_squared_distribution<RealType = double> withn degrees offreedom is the distribution of a sum of the squares ofn independentstandard normal random variables.

Note that even though the distribution's parametern usually is anintegral value, it doesn't have to be integral, as the chi_squareddistribution is defined in terms of functions (exp andGamma) thattake real arguments (see, e.g., the formula shown in the<bits/random.h>header file, provided with the GNUg++ compiler distribution).

The chi-squared distribution is used, e.g., when testing the goodness of fitof an observed distribution to a theoretical one.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType n = RealType(1));        RealType n() const;    };

Constructors and members:

18.9.2.5: Extreme value distribution

Theextreme_value_distribution<RealType = double> is related to theWeibull distribution and is used in statistical models where the variable ofinterest is the minimum of many random factors, all of which can take positiveor negative values.

It has two parameters: a location parametera and scale parameterb.See also
http://www.itl.nist.gov/div898/handbook/apr/section1/apr163.htm

Defined types:

using result_type = RealType;struct param_type{    explicit param_type(RealType a = RealType(0),                        RealType b = RealType(1));    RealType a() const;     // the location parameter    RealType b() const;     // the scale parameter};

Constructors and members:

18.9.2.6: Exponential distribution

Theexponential_distribution<RealType = double> is used to describe thelengths between events that can be modeled with a homogeneous Poissonprocess. It can be interpreted as the continuous form of thegeometric distribution.

Its parameterprob defines the distribution'slambda parameter, calleditsrate parameter. Its expected value and standard deviation are both1 / lambda.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType lambda = RealType(1));        RealType lambda() const;    };

Constructors and members:

18.9.2.7: Fisher F distribution

Thefisher_f_distribution<RealType = double> is intensively used instatistical methods like the Analysis of Variance. It is the distributionresulting from dividing twoChi-squared distributions.

It is characterized by two parameters, being the degrees of freedom of the twochi-squared distributions.

Note that even though the distribution's parametern usually is anintegral value, it doesn't have to be integral, as the Fisher F distributionis constructed from Chi-squared distributions that accept a non-integralparameter value (see also section18.9.2.4).

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType m = RealType(1),                            RealType n = RealType(1));        RealType m() const; // The degrees of freedom of the nominator        RealType n() const; // The degrees of freedom of the denominator    };

Constructors and members:

18.9.2.8: Gamma distribution

Thegamma_distribution<RealType = double> is used when working with datathat are not distributed according to the normal distribution. It is oftenused to model waiting times.

It has two parameters,alpha andbeta. Its expected value isalpha* beta and its standard deviation isalpha * beta2.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType alpha = RealType(1),                            RealType beta = RealType(1));        RealType alpha() const;        RealType beta() const;    };

Constructors and members:

18.9.2.9: Geometric distribution

Thegeometric_distribution<IntType = int> is used to model the numberof bernoulli trials (cf.18.9.2.1) needed until the first success.

It has one parameter,prob, representing the probability of success in anindividual bernoulli trial.

Defined types:

    using result_type = IntType;    struct param_type    {        explicit param_type(double prob = 0.5);        double p() const;    };

Constructors, members and example:

18.9.2.10: Log-normal distribution

Thelognormal_distribution<RealType = double> is a probabilitydistribution of a random variable whose logarithm is normally distributed. Ifa random variableX has a normal distribution, thenY = eX has alog-normal distribution.

It has two parameters,m ands representing, respectively, the meanand standard deviation ofln(X).

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType m = RealType(0),                            RealType s = RealType(1));        RealType m() const;        RealType s() const;    };

Constructor and members:

18.9.2.11: Normal distribution

Thenormal_distribution<RealType = double> is commonly used in science todescribe complex phenomena. When predicting or measuring variables, errors arecommonly assumed to be normally distributed.

It has two parameters,mean andstandard deviation.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType mean = RealType(0),                            RealType stddev = RealType(1));        RealType mean() const;        RealType stddev() const;    };

Constructors and members:

18.9.2.12: Negative binomial distribution

Thenegative_binomial_distribution<IntType = int> probability distributiondescribes the number of successes in a sequence of Bernoulli trials before aspecified number of failures occurs. For example, if one throws a dierepeatedly until the third time 1 appears, then the probability distributionof the number of other faces that have appeared is a negative binomialdistribution.

It has two parameters: (IntType) k (> 0), being the number of failuresuntil the experiment is stopped and (double) p the probability of successin each individual experiment.

Defined types:

    using result_type = IntType;    struct param_type    {        explicit param_type(IntType k = IntType(1), double p = 0.5);        IntType k() const;        double p() const;    };

Constructors and members:

18.9.2.13: Poisson distribution

Thepoisson_distribution<IntType = int> is used to model the probabilityof a number of events occurring in a fixed period of time if these eventsoccur with a known probability and independently of the time since the lastevent.

It has one parameter,mean, specifying the expected number of events inthe interval under consideration. E.g., if on average 2 events are observed ina one-minute interval and the duration of the interval under study is10 minutes thenmean = 20.

Defined types:

    using result_type = IntType;    struct param_type    {        explicit param_type(double mean = 1.0);        double mean() const;    };

Constructors and members:

18.9.2.14: Student t distribution

Thestudent_t_distribution<RealType = double> is a probabilitydistribution that is used when estimating the mean of a normally distributedpopulation from small sample sizes.

It is characterized by one parameter: the degrees of freedom, which is equalto the sample size - 1.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType n = RealType(1));        RealType n() const;    // The degrees of freedom    };

Constructors and members:

18.9.2.15: Uniform int distribution

Theuniform_int_distribution<IntType = int> can be used to select integralvalues randomly from a range of uniformly distributed integral values.

It has two parameters,a andb, specifying, respectively, the lowestvalue that can be returned and the highest value that can be returned.

Defined types:

    using result_type = IntType;    struct param_type    {        explicit param_type(IntType a = 0, IntType b = max(IntType));        IntType a() const;        IntType b() const;    };

Constructors and members:

18.9.2.16: Uniform real distribution

Theuniform_real_distribution<RealType = double> can be used to selectRealType values randomly from a range of uniformly distributedRealType values.

It has two parameters,a andb, specifying, respectively, thehalf-open range of values ([a, b)) that can be returned by thedistribution.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType a = 0, RealType b = max(RealType));        RealType a() const;        RealType b() const;    };

Constructors and members:

18.9.2.17: Weibull distribution

Theweibull_distribution<RealType = double> is commonly used inreliability engineering and in survival (life data) analysis.

It has two or three parameters and the two-parameter variant is offered by theSTL. The three parameter variant has a shape (or slope) parameter, a scaleparameter and a location parameter. The two parameter variant implicitly usesthe location parameter value 0. In the two parameter variant the shapeparameter (a) and the scale parameter (b) are provided. See
http://www.weibull.com/hotwire/issue14/relbasics14.htm for aninteresting coverage of the meaning of the Weibull distribution's parameters.

Defined types:

    using result_type = RealType;    struct param_type    {        explicit param_type(RealType a = RealType{ 1 },                            RealType b = RealType{ 1 });        RealType a() const;     // the shape (slope) parameter        RealType b() const;     // the scale parameter    };

Constructors and members:

18.10: tie

We already encounteredstructured bindings in section3.3.7.1. Structured bindings allow us to access the fields ofstructured types (likestructs, std::pair or (cf. section22.6)tuples) as local variables inside functions. A basic example usingstructured bindings is shown in the following code snippet:
    pair<int, int> factory()    {        return { 1, 2 };    }        void fun()    {        auto [one, two] = factory();            cout << one << ' ' << two << '\n';    }

Being able to use structured bindings is very useful in cases like these.

But what if we want to assign the fields of a struct to variables that havealready been defined or that were passed to a function via its parameters? Inthose situations structured bindings offer no help. E.g., in the followingcode snippet a functionretrieve is defined having anint & parameterand anint local variable and we want to assign the values returned byfactory to those variables:

    void retrieve(int &one)    {        int two;        // ...  = factory() ??    }

Structured bindings cannot be used here: the elements of structured bindingscannot be references. Although itis possible to define astd::pair<int&, int &> such an object cannot be initialized with the references ofoneandtwo which are directly referring to the fields returned byfactory. These statements won't compile:

    pair<int &, int &> p{one, two} = factory();    pair<int &, int &>{one, two} = factory();

While it is possible to first define apair<int &, int &> object and thenassignfactory's return value to it, that approach clearly is less elegantthan what's offered by structured bindings:

    pair<int &, int &> p{one, two};    p = factory();

Fortunately, there is a better alternative. After including the<tuple>header file (see also section22.6)std::tie is available allowingus to `tie' references to fields of structured data types. Usingstd::tieit is very easy to associate the variablesone andtwo of the functionretrieve with the fields of the pair returned byfactory:

    void retrieve(int &one)    {        int two;            tie(one, two) = factory();            cout << one << ' ' << two << '\n';    }

When Executing these statements:

    int one = 0;    int two = 0;    cout << one << ' ' << two << '\n';    retrieve(one);    cout << one << ' ' << two << '\n';

the following output is obtained:

    0 0    1 2    1 0

In addition to the above thestd::tie function also supports ordering and(in)equality comparisons. Thestruct Data in the next example definesthree fields: anint, astd::string and adouble. Each of thesefields support ordering and (in)equality comparisons. In those cases, allcomparison operators can easily be implemented through the spaceshipoperator (cf. section11.7.2) usingstd::tie:

    struct Data    {        int d_int;        string d_string;        double d_double;    };            bool operator==(Data const &lhs, Data const &rhs)    {        return tie(lhs.d_int, lhs.d_string, lhs.d_double) ==               tie(rhs.d_int, rhs.d_string, rhs.d_double);    }        partial_ordering operator<=>(Data const &lhs, Data const &rhs)    {        return tie(lhs.d_int, lhs.d_string, lhs.d_double) <=>               tie(rhs.d_int, rhs.d_string, rhs.d_double);    }

Note thatstruct Data's spaceship operator returnspartial_orderingvalues (cf. section18.7.3). Althoughint andstd::string'sspaceship operators returnstrong_ordering values,double's spaceshipoperator doesn't. Instead it returnspartial_orderingvalues. Consequently,struct Data's spaceship operator also returnspartial_ordering values.

18.11: Optional return values

In order to usestd::optional objects the<optional> header file mustbe included.

Consider a function returning subsequent lines from a stream. That functioncould be a member function reading from a stream which was opened by itsobject. A first implementation of such a member function could be

    std::string Class::nextLine()    {        string line;        getline(d_stream, line);        return line;    }
Of course, this implementation is sub-optimal asgetline may fail.

Common ways to handle failures in these situations are

The standard template library offers yet another way to handle situations likethese: the template class

    template <typename DataType>    class optional;
Here,DataType refers to the name of the data type that is handled bytheoptional class. Instead of returning astd::string the functionnextLine may specify astd::optional<std::string> return type:std::optional<std::string> Class::nextLine().

The interpretation ofstd::optional objects is simple: either it containsan object of itsDataType or it doesn't. If itdoes contain aDataType object then that object is available as object instead of apointer to an object (which might have been dynamically allocated) of thespecifiedDataType. At the same type theoptional object can beinterpreted as abool. If theoptional object contains aDataTypeobject theoptional's bool value istrue. If it doesn't contain aDataType value, then itsbool value isfalse.

The classstd::optional offers the following facilities:

Here is the implementation of a functionnextLine, usingstd::optional<std::string> and a simplemain function illustrating itsworkings:

    #include <iostream>    #include <sstream>    #include <string>    #include <optional>    using namespace std;    optional<string> nextLine(istream &in)    {        std::optional<std::string> opt;        string line;        if (getline(in, line))            opt = move(line);        cout << "internal: has value: " << opt.has_value() <<                                    ", value = " << *opt << '\n';        return opt;    }    int main()    {        istringstream in{ "hello world\n" };        auto opt = nextLine(in);        cout << "main:     has value: " << opt.has_value() <<                                    ", value = " << *opt << '\n';        opt = nextLine(in);        cout << "main:     has value: " << opt.has_value() <<                                    ", value = " << *opt << '\n';    }

The ouput of this program is:

    internal: has value: 1, value = hello world    main:     has value: 1, value = hello world    internal: has value: 0, value =     main:     has value: 0, value = hello world
Note that after the 2nd call, when no value is returned,opt has kept thevalue it received at the first call:optional's assignment operatordoesn't bother about values already present in their objects once it noticesthathas_value will returnfalse. So be sure to inspecthas_valueoroperator bool before callingvalue.




[8]ページ先頭

©2009-2025 Movatter.jp