~ubuntu-branches/ubuntu/trusty/c++-annotations/trusty-proposed

« back to all changes in this revision

Viewing changes to yo/stl/lambda.yo

  • Committer: Package Import Robot
  • Author(s): Frank B. Brokken
  • Date: 2013-05-30 13:32:18 UTC
  • mfrom: (1.1.24)
  • Revision ID: package-import@ubuntu.com-20130530133218-k39mr5uredd093jr
Tags: 9.7.2-1
New upstream release, repairs several minor left-over flaws.
This release incorporates 9.7.0 and 9.7.1.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
The C++11 standard has added hi(lambda function) em(lambda functions) to
2
 
bf(C++). As we've seen generic algorithms often accept an argument that can
3
 
either be a function object or it can be a plain function. Examples are the
4
 
tt(sort) and tt(find_if) generic algorithms. When the function called by the
5
 
generic algorithm must remember its state a function object is appropriate,
6
 
otherwise a plain function can be used.
 
1
The C++11 standard has added hi(lambda expression) em(lambda expressions) to
 
2
the language. As we've seen generic algorithms often accept an argument that
 
3
can either be a function object or it can be a plain function. Examples are
 
4
the tt(sort) and tt(find_if) generic algorithms. When the function called by
 
5
the generic algorithm must remember its state a function object is
 
6
appropriate, otherwise a plain function can be used.
7
7
 
8
8
The function or function object is usually not readily available. Often it
9
9
must be defined in or near the location where the generic algorithm is used.
10
 
The software engineer usually accomplished this by defining a class or
11
 
function in the anonymous namespace, passing an object of that class or
12
 
passing that function to a generic algorithm called from some other
13
 
function. If the latter function is itself a member function the need may be
14
 
felt to grant the function called by the generic algorithm access to other
15
 
members of its class. Often this results in a significant amount of code
16
 
(defining the class); or it results in complex code (to make available
17
 
software elements that aren't automatically accessible from the called
18
 
function (object)); and it may result -at the level of the source file- in
19
 
code that is irrelevant at the current level of specification. Nested classes
20
 
don't solve these problems and nested classes can't be used in templates.
21
 
 
22
 
A i(lambda function) is an
23
 
    i(anonymous function)hi(function: anonymous). Such a function may be
24
 
defined on the spot and exists only during the lifetime of the statement
25
 
of which it is a part.
26
 
 
27
 
Here is an example of a lambda function:
 
10
Usually this is accomplished by defining a class or function in the anonymous
 
11
namespace, passing an object of that class or passing that function to a
 
12
generic algorithm called from some other function. If the latter function is
 
13
itself a member function the need may be felt to grant the function called by
 
14
the generic algorithm access to other members of its class. Often this results
 
15
in a significant amount of code (defining the class), or it results in complex
 
16
code (to make available software elements that aren't automatically accessible
 
17
from the called function (object)). It may also result in code that is
 
18
irrelevant at the current level of specification. Nested classes don't solve
 
19
these problems and nested classes can't be used in templates.
 
20
 
 
21
A i(lambda expression) defines an
 
22
    i(anonymous function object)hi(function object: anonymous), also called a
 
23
    emi(closure object). When a lambda expression is evaluated it results in a
 
24
temporary object (the closure object). The type of a closure object is called
 
25
its emi(closure type). 
 
26
 
 
27
Lambda expressions may be used inside blocks, classes or namespaces (i.e.,
 
28
pretty much anywhere you like). Their implied closure type is defined in the
 
29
smallest block, class or namespace scope which contains the lamba
 
30
expression. The closure object's visibility starts at its point of definition
 
31
and ends where its closure type ends.
 
32
 
 
33
The closure type defines a (tt(const)) public inline function call
 
34
operator. Here is an example of a lambda expression:
 
35
    hi(lambda-introducer)
 
36
    hi(lambda-declarator)
28
37
        verb(
29
 
    [](int x, int y)
30
 
    {
 
38
    []                      // the `lambda-introducer'
 
39
    (int x, int y)          // the `lambda-declarator'
 
40
    {                       // a normal compound-statement
31
41
        return x * y;
32
42
    }
33
43
        )
34
 
    This particular function expects two tt(int) arguments and returns their
35
 
product. It could be used e.g., in combination with the tt(accumulate) generic
36
 
algorithm  to compute the product of a series of tt(int) values stored in a
37
 
vector:
 
44
    The function call operator of the closure object created by this lambda
 
45
expression expects two tt(int) arguments and returns their product. It is an
 
46
inline tt(const) member of the closure type. To drop the tt(const) attribute,
 
47
the lamba expression should specify tt(mutable), as follows:
 
48
        verb(
 
49
    [](int x, int y) mutable
 
50
    ...
 
51
        )
 
52
    The lambda-declarator may be omitted, if it does not contain
 
53
parameters. The parameters in a lamba declarator may not be provided with
 
54
default arguments.
 
55
 
 
56
 A closure object as defined by the above lamda expression could be used e.g.,
 
57
in combination with the tt(accumulate) generic algorithm to compute the
 
58
product of a series of tt(int) values stored in a vector:
38
59
        verb(
39
60
    cout << accumulate(vi.begin(), vi.end(), 1,
40
61
                [](int x, int y) { return x * y; });
41
62
        )
42
63
    The above lambda function uses the implicit return
43
64
        hi(return type: implicit)
44
 
    type tt(decltype(x * y)). An implicit return type can
45
 
only be used if the lambda function has a single statement of the form
46
 
    tt(return expression;).
 
65
    type tt(decltype(x * y)). An implicit return type can be used if the
 
66
lambda expression does not contain a tt(return) stattement (i.e., a void
 
67
lambda expression), if it contains a single tt(return) statement, or if it
 
68
contains multiple tt(return) statements returning values of identical types
 
69
(e.g., all tt(int) values). 
47
70
 
48
 
    Alternatively, the return type can be explicitly specified using a
 
71
    If there are multiple tt(return) statements returning values of different
 
72
types then the lambda expression's return type must specified be explicitly
 
73
using a
49
74
        hi(return type: late-specified)i(late-specified return type),
50
75
(cf. section ref(AUTO)):
51
76
        verb(
52
77
    [](int x, int y) -> int
53
78
    {
54
 
        int z = x + y;
 
79
        if (y < 0)
 
80
            return x / static_cast<double>(y);
 
81
 
55
82
        return z + x;
56
83
    }
57
84
        )
58
 
    There is no need to specify a return type for lambda functions that do
59
 
not return values (i.e., a void lambda function).
60
 
 
61
 
    Variables having the same scope as the lambda function can be accessed
62
 
from the lambda function using references, declared in between the lambda
63
 
function's square brackets. This allows passing the
64
 
    i(local context) to lambda functions. Such variables are called a
65
 
emi(closure). Here is an example:
66
 
        verb(
67
 
    void showSum(vector<int> &vi)
 
85
 
 
86
    Variables that are visible at the location of a lambda expression can be
 
87
accessed by the lambda expression. How these variables are accessed depends on
 
88
the contents of the lambda-introducer (the area between the square brackets,
 
89
called the the emi(lambda-capture)). The lambda-capture allows passing a
 
90
    i(local context) to lambda expressions. 
 
91
 
 
92
    Visible global and static variables as well as local variables defined in
 
93
the lambda expression's compound statement itself can directly be accessed
 
94
and, if applicable, modified. Example:
 
95
        verb(
 
96
    int global;
 
97
    
 
98
    void fun()
 
99
    {
 
100
        []()  // [] may contain any specification
 
101
        { 
 
102
            int localVariable = 0;
 
103
            localVariable = ++global; 
 
104
        };
 
105
    }
 
106
        )
 
107
 
 
108
    If the lambda expression is defined within a (non-static) class member
 
109
function then an initial tt(&) or tt(=) character in the lambda-capture
 
110
enables tt(this), allowing the lambda expression access to all class members
 
111
(data and functions).  The class's data members can be modified.
 
112
 
 
113
    If the lambda expression is defined inside a function then that function's
 
114
local variables that are visible at the point of the lambda expression's
 
115
definition can be accessed by the lambda expression.
 
116
 
 
117
    An initial tt(&) character in the lambda-capture accesses these local
 
118
variables by reference. These variables can be modified from within the lambda
 
119
expression.
 
120
 
 
121
    An initial tt(=) character in the lambda-capture creates a local copy of
 
122
the referred-to local variables. Furthermore, in this case the values of these
 
123
local copies can only be changed by the lambda expression if the lambda
 
124
expression is defined using the tt(mutable) keyword. E.g.,
 
125
        verb(
 
126
    struct Class
 
127
    {
 
128
        void fun()
 
129
        {
 
130
            int var = 0;
 
131
            [=]() mutable
 
132
            {
 
133
                ++var;  // modifies the local
 
134
            }           // copy, not fun's var
 
135
        }
 
136
    }
 
137
        )
 
138
 
 
139
    Fine-tuning is also possible. With an initial tt(=), comma-separated
 
140
tt(&var) specifications indicate that the mentioned local variables should be
 
141
processed by reference, rather than as copies; with an initial tt(&), comma
 
142
separated tt(var) specifications indicate that local copies should be used of 
 
143
the mentioned local variables. Again, these copies have immutable values
 
144
unless the lambda expression is provided with the tt(mutable) keyword. 
 
145
 
 
146
Here is an example:
 
147
        verb(
 
148
    void showSum(vector<int> const &vi)
68
149
    {
69
150
        int total = 0;
70
151
        for_each(
71
152
            vi.begin(), vi.end(),
72
 
            [&total](int x)
 
153
            [&](int x)
73
154
            {
74
155
                total += x;
75
156
            }
76
157
        );
77
 
        std::cout << total;
78
 
    }
79
 
        )
80
 
    The variable tt(int total) is passed to the lambda function as a reference
81
 
(tt([&total])) and can directly be accessed by the function. Its parameter
82
 
list merely defines an tt(int x), which is initialized in sequence by each of
83
 
the values stored in tt(vi). Once the generic algorithm has completed
84
 
tt(showSum)'s variable tt(total) has received a value that is equal to the sum
85
 
of all the vector's values. It has outlived the lambda function and its value
86
 
is displayed.
87
 
 
88
 
    If a closure variable is defined without the reference symbol (tt(&)) it
89
 
is interpreted as a value parameter of the lambda function that is initialized
90
 
by the local variable when the lambda function is passed to the generic
91
 
algorithm. Usually closure variables are passed by reference. If em(all) local
92
 
variables are to be passed by reference a mere ti([&]) can be used (to
93
 
pass the full closure by value ti([=]) should be used):
94
 
        verb(
95
 
    void show(vector<int> &vi)
96
 
    {
97
 
        int sum = 0;
98
 
        int prod = 1;
99
 
        for_each(
100
 
            vi.begin(), vi.end(),
101
 
            [&](int x)
102
 
            {
103
 
                sum += x;
104
 
                prod *= x;
105
 
            }
106
 
        );
107
 
        std::cout << sum << ' ' << prod;
108
 
    }
109
 
        )
110
 
    COMMENT(
111
 
The specific internal implementation can vary, but the expectation is that the
112
 
lambda function will store the actual stack pointer of the function it is
113
 
created in, rather than individual references to stack variables.
114
 
    END )
115
 
 
116
 
    It is also possible to pass some variables to lambda function by value and
117
 
other variables by reference.  In that case the default is specified using
118
 
tt(&) or tt(=). This default specifying symbol is then followed by a list of
119
 
variables passed differently. Example:
120
 
        verb(
121
 
    [&, value](int x)
122
 
    {
123
 
        total +=  x * value;
124
 
    };
125
 
        )
126
 
    Here  tt(total) is passed by reference, tt(value) by value.
127
 
 
128
 
    Class members may also define lambda functions. Such lambda functions have
129
 
full access to all the class's members. In other words, they are automatically
130
 
defined as friends of the class. Example:
 
158
        std::cout << total << '\n';
 
159
    }
 
160
        )
 
161
    The variable tt(int total) is passed to the lambda expression by reference
 
162
and is directly accessed by the function. Its parameter list merely defines an
 
163
tt(int x), which is initialized in sequence by each of the values stored in
 
164
tt(vi). Once the generic algorithm has completed tt(showSum)'s variable
 
165
tt(total) has received a value that is equal to the sum of all the vector's
 
166
values. It has outlived the lambda expression and its value is displayed.
 
167
 
 
168
Another fine-tuning consists of specifying tt(this) in the lambda-capture: it
 
169
also allows the lambda-expression to access the surrounding class members.
 
170
Example:
131
171
        verb(
132
172
    class Data
133
173
    {
139
179
                std::for_each(d_names.begin(), d_names.end(),
140
180
                    [this, &count](std::string const &name)
141
181
                    {
142
 
                        std::cout << ++count <<
143
 
                            this->capitalized(name) << '\n';
 
182
                        std::cout << ++count << ' ' <<
 
183
                            capitalized(name) << '\n';
144
184
                    }
145
185
                );
146
186
            }
147
187
        private:
148
 
            std::string capitalized(std::string const &name);
 
188
            std::string capitalized(std::string name);
149
189
    };
150
190
        )
151
 
    Note the use of the tt(this) pointer: inside the lambda function it must
152
 
explicitly be used (so,nl()
153
 
        tt(this->capitalized(name)) is used rather than
154
 
tt(capitalized(name))). In addition, in situations like these tt(this)
155
 
is automatically available when either tt([&]) or tt([=]) is used. So,
156
 
the above lambda function can also be be defined as:
157
 
        verb(
158
 
                    [&](std::string const &name)
 
191
 
 
192
    Lambda expressions may also be assigned to variables. An example of such
 
193
an assignment (using tt(auto) to define the variable's type) is:
 
194
        verb(
 
195
    auto sqr = [](int x)
 
196
               {
 
197
                   return x * x;
 
198
               };
 
199
        )
 
200
    The lifetime of such lambda expressions is equal to the lifetime of the
 
201
variable receiving the lambda expression as its value. Named lambda functions
 
202
nicely fit in the niche of local functions: when a function needs to perform
 
203
some computations that are at a conceptually lower level than the function's
 
204
task itself, then it's attractive to encapsulate these computations in a
 
205
separate support function, and call the support function where needed.  A
 
206
support function can be defined in an anonymous namespace, but that quickly
 
207
becomes awkward when the requiring function is a class member, and the support
 
208
function needs access to the class's members as well. In that case a named
 
209
lambda expression can be used: it can be defined within the requiring
 
210
function, and may be given full access to the surrounding class. The name to
 
211
which the lambda expression is assigned becomes the name of a function which
 
212
can be called from the surrounding function. Here is an example, converting a
 
213
numeric IP address to a dotted decimal string, which can also be accessed
 
214
directly from an tt(Dotted) object (all implementations in-class to conserve
 
215
space):
 
216
        verb(
 
217
    class Dotted
 
218
    {
 
219
        std::string d_dotted;
 
220
        
 
221
        public:
 
222
            std::string const &dotted() const
 
223
            {
 
224
                return d_dotted;
 
225
            }
 
226
            std::string const &dotted(size_t ip)
 
227
            {
 
228
                auto octet = 
 
229
                    [](size_t idx, size_t numeric)
159
230
                    {
160
 
                        std::cout << ++count <<
161
 
                            this->capitalized(name) << '\n';
162
 
                    }
 
231
                        return to_string(numeric >> idx * 8 & 0xff);
 
232
                    };
 
233
 
 
234
                d_dotted = 
 
235
                        octet(3, ip) + '.' + octet(2, ip) + '.' +
 
236
                        octet(1, ip) + '.' + octet(0, ip);
 
237
 
 
238
                return d_dotted;
 
239
            }
 
240
    };
163
241
        )
164
242
 
165
 
    Lambda functions may be assigned to variables. An example of such an
166
 
assignment (using tt(auto) to define the variable's type) is:
 
243
Now that lambda expressions have been covered let's see how they can be used
 
244
to avoid spurious returns from tt(condition_variable's wait) calls
 
245
(cf. section ref(CONDEX)). According to the C++11 standard, condition
 
246
variables may spuriously return from tt(wait) calls. Therefore it is necessary
 
247
to check that the data are actually available once tt(wait) continues. 
 
248
 
 
249
The class tt(condition_variable) allows us to do that by offering tt(wait)
 
250
members expecting a lock em(and) a predicate. The predicate checks the data's
 
251
state, and returns tt(true) if the data's state allows the data's
 
252
processing. Here is an alternative implementation of the tt(down) member shown
 
253
in section ref(CONDEX), checking for the data's actual availability:
167
254
        verb(
168
 
    auto lambdaFun = [this]()
169
 
                     {
170
 
                        this->member();
171
 
                     };
 
255
    void down()
 
256
    {
 
257
        unique_lock<mutex> lock(sem_mutex);
 
258
        condition.wait(lock, 
 
259
            [&]()
 
260
            {
 
261
                return semaphore != 0
 
262
            }
 
263
        );
 
264
        --semaphore;
 
265
    }
172
266
        )
173
 
    The lifetime of such lambda functions is equal to the lifetime of the
174
 
variable receiving the lambda function as its value.
 
267
    The lambda expression ensures that tt(wait) only returns once
 
268
tt(semaphore) has been incremented.
 
269
    
 
270