File: lambdause.yo

package info (click to toggle)
c%2B%2B-annotations 10.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 10,536 kB
  • ctags: 3,247
  • sloc: cpp: 19,157; makefile: 1,521; ansic: 165; sh: 128; perl: 90
file content (202 lines) | stat: -rw-r--r-- 7,889 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    Now that the syntax of lambda expressions have been covered let's see how
they can be used in various situations.

First we consider named lambda expressions. Named lambda expressions nicely
fit in the niche of hi(function:local)
        hi(local function)
    local functions: when a function needs to perform computations which are
at a conceptually lower level than the function's task itself, then it's
attractive to encapsulate these computations in a separate support function
and call the support function where needed.  Although support functions can be
defined in anonymous namespaces, that quickly becomes awkward when the
requiring function is a class member and the support function also must access
the class's members.

In that case a named lambda expression can be used: it can be defined inside
a requiring function, and it may be given full access to the surrounding
class. The name to which the lambda expression is assigned becomes the name of
a function which can be called from the surrounding function. Here is an
example, converting a numeric IP address to a dotted decimal string, which can
also be accessed directly from an tt(Dotted) object (all implementations
in-class to conserve space):
        verb(
    class Dotted
    {
        std::string d_dotted;
        
        public:
            std::string const &dotted() const
            {
                return d_dotted;
            }
            std::string const &dotted(size_t ip)
            {
                auto octet = 
                    [](size_t idx, size_t numeric)
                    {
                        return to_string(numeric >> idx * 8 & 0xff);
                    };

                d_dotted = 
                        octet(3, ip) + '.' + octet(2, ip) + '.' +
                        octet(1, ip) + '.' + octet(0, ip);

                return d_dotted;
            }
    };
        )

Next we consider the use of generic algorithms, like 
the tt(for_each) (cf. section ref(FOREACH)):
        verb(
    void showSum(vector<int> const &vi)
    {
        int total = 0;
        for_each(
            vi.begin(), vi.end(),
            [&](int x)
            {
                total += x;
            }
        );
        std::cout << total << '\n';
    }
        )
    Here the variable tt(int total) is passed to the lambda expression by
reference and is directly accessed by the function. Its parameter list merely
defines an tt(int x), which is initialized in sequence by each of the values
stored in tt(vi). Once the generic algorithm has completed tt(showSum)'s
variable tt(total) has received a value that is equal to the sum of all the
vector's values. It has outlived the lambda expression and its value is
displayed.

But although generic algorithms are extremely useful, there may not always be
one that fits the task at hand. Furthermore, an algorithm like tt(for_each)
looks a bit unwieldy, now that the language offers range-based for-loops. So
let's try this, instead of the above implementation:
        verb(
    void showSum(vector<int> const &vi)
    {
        int total = 0;
        for (auto el: vi)
            [&](int x)
            {
                total += x;
            };

        std::cout << total << '\n';
    }
        )
    But when tt(showSum) is now called, its tt(cout) statement consistently
reports 0. What's happening here?

    When a generic algorithm is given a lambda function, its implementation
instantiates a reference to a function. that referenced function is thereupon
called from within the generic algorithm. But, in the above example the
range-based for-loop's nested statement merely represents the em(defintion)
of a lamba function. Nothing is actually called, and hence tt(total) remains
equal to 0. 

    Thus, to make the above example work we not only must em(define) the
lambda expression, but we must also em(call) the lambda function. We can do
this by  giving the lambda function a em(name), and then call the
lamba function by its given name:
        verb(
    void showSum(vector<int> const &vi)
    {
        int total = 0;
        for (auto el: vi)
        {
            auto lambda = [&](int x)
                            {
                                total += x;
                            };

            lambda(el);
        }
        std::cout << total << '\n';
    }
        )
    
In fact, there is no need to give the lambda function a name: the tt(auto
lambda) definition represents the lambda function, which could also
directly be called. The syntax for doing this may look a
bit weird, but there's nothing wrong with it, and it allows us to drop the
compound statment, required in the last example, completely. Here goes:
        verb(
    void showSum(vector<int> const &vi)
    {
        int total = 0;
        for (auto el: vi)
            [&](int x)
            {
                total += x;
            }(el);          // immediately append the 
                            // argument list to the lambda
                            // function's definition
        std::cout << total << '\n';
    }
        )

lambda expressions can also be used to prevent spurious returns from
tt(condition_variable's wait) calls (cf. section ref(CONDEX)). 

The class tt(condition_variable) allows us to do so by offering tt(wait)
members expecting a lock em(and) a predicate. The predicate checks the data's
state, and returns tt(true) if the data's state allows the data's
processing. Here is an alternative implementation of the tt(down) member shown
in section ref(CONDEX), checking for the data's actual availability:
        verb(
    void down()
    {
        unique_lock<mutex> lock(sem_mutex);
        condition.wait(lock, 
            [&]()
            {
                return semaphore != 0
            }
        );
        --semaphore;
    }
        )
    The lambda expression ensures that tt(wait) only returns once
tt(semaphore) has been incremented.

    
Lambda expression are primarily used to obtain functors that are used in a
very localized section of a program. Since they are used inside an existing
function we should realize that once we use lambda functions multiple
aggregation levels are mixed. Normally a function implements a task which can
be described at its own aggregation level using just a few sentences. E.g.,
``the function tt(std::sort) sorts a data structure by comparing its elements
in a way that is appropriate to the context where tt(sort) is called''. By
using an existing comparison method the aggregation level is kept, and the
statement is clear by itself. E.g.,
        verb(
    sort(data.begin(), data.end(), greater<DataType>());
        )
    If an existing comparison method is not available, a tailor-made function
object must be created. This could be realized using a lambda
expression. E.g.,
        verb(
    sort(data.begin(), data.end(), 
        [&](DataType const &lhs, DataType const &rhs)
        {
            return lhs.greater(rhs);
        }
    );
        )
    Looking at the latter example, we should realize that here two different
aggregation levels are mixed: at the top level the intent is to sort the
elements in tt(data), but at the nested level (inside the lambda expression)
something completely different happens. Inside the lambda expression we define
how a the decision is made about which of the two objects is the greater. Code
exhibiting such mixed aggregation levels is hard to read, and should be
avoided. 

On the other hand: lambda expressions also simplify code because the overhead
of defining a tailor-made functor is avoided. The advice, therefore, is to use
lambda expressions sparingly. em(When) they are used make sure that their
sizes remain small. As a i(rule of thumb): lambda expressions should be
treated like in-line functions, and should merely consist of one, or maybe
occasionally two expressions.