Caveat: You aren’t going to be tested on lambda capture lists (afaik), and you’d probably NOT use this syntax very much in your assignments.
However, lambdas were covered briefly during tutorial because you’d see many funky lambda declarations in the wild
on StackOverflow while you are debugging (for your assignment). This is to help aid your understanding while reading concurrent c++ code in the real world.
There was some confusion to the behaviour of lambda capture list behaviour, so I will elaborate on them here.
You can compile and verify the code snippets yourself locally.
When in doubt, consult cppreference as the source of truth.
Focus on the “Explanation” section.
Note that a compile-able C++ snippet is dumped at the very end of the page for you to run locally.
Copy, Reference, Default
&
means variables with automatic storage duration are captured by reference by default.
=
means variables with automatic storage duration are captured by copy by default.
&foo
, where foo
is a variable indicates that the variable is explicitly captured by reference.
=foo
, where foo
is a variable indicates that the variable is explicitly captured by copy.
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// Pass by copy
int foo = 0;
std::thread t1([=](){
printf("%d\n", foo); // ok
// foo++ // this is not possible
do_something(foo); // ok
})
// Pass by reference
foo = 0;
std::thread t1([&](){
printf("%d\n", foo); // ok
foo++ // ok
do_something(foo); // ok
})
|
This does not deviate much from what was covered in the tutorial.
1. Default behaviour of “[]”
Nothing is captured. But this does not apply to global variables. The reason is covered in ‘2.’
0
1
2
3
4
5
6
7
|
int global = 0;
void foo() {
int local = 0;
std::thread t1([](){
// do_something(local); // not possible as [] does not capture anything, including local
do_something(global); // possible since global has static storage duration
})
}
|
2. Global variables cannot be ‘captured’ but can be used (this is evil)
Global variables can be used inside a lambda even when ‘[=]’ or ‘[]’ is used because they aren’t actually captured by the lambda.
cppreference:
A lambda expression can use a variable without capturing it if the variable…
is a non-local variable or has static or thread-local storage duration
Global variables have static storage duration. So,
0
1
2
3
4
5
6
7
8
9
10
11
|
int global = 0;
void foo() {
int local = 0;
std::thread t1([=](){
int lambda_local = 0;
lambda_local++; // OK
// local++; // NOT OK, local captured as const copy
global++; // OK - global
})
}
|
3. Mixing =, & and more
There are combinations of reference and copy you can use.
You can explicitly say that you want to capture a variable by reference or copy:
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// foo, bar, baz are NOT global variables here
int foo = 0;
int bar = 0;
int baz = 0;
std::thread t2([&foo, bar](){
// foo is captured by reference
// bar is captured by copy
// baz is NOT captured
do_something(foo, bar);
});
std::thread t3([=, &foo](){
// foo is captured by reference
// bar is captured by copy (by default due to =)
// baz is captured by copy (by default due to =)
do_something_else(foo, bar, baz);
});
std::thread t4([&, bar](){
// foo is captured by reference (by default due to &)
// bar is captured by copy
// baz is captured by reference (by default due to &)
do_another_thing(foo, bar, baz);
});
|
For this class, you really don’t need such syntax. Use it with caution if you really need to.
Compileable code for reference
This script illustrates the capture behaviour for [=], [&] and [].
Mixing explicitly capturing by copy or reference is omitted in this example for simplicity.
0
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
|
#include <thread>
#include <iostream>
void print_n(int n){
printf("%d\n", n);
}
void add_one_print_n(int n){
n++;
printf("%d\n", n);
}
void print_n_ref(int& n){
printf("%d\n", n);
}
void add_one_print_n_ref(int& n){
n++;
printf("%d\n", n);
}
int global = 0; // zero initialize this
int main() {
int local = 0;
// DEFAULT BEHAVIOUR (Empty capture list)
// Notice how `local` in the adjacent scope of main() cannot be captured
// So, the default behaviour is NOT PASS BY COPY. You simply just can't capture.
// This is resolved with the later examples "[=]" and "[&]",
// where the default behaviour is specified.
std::thread default_t1([](){
printf("=== Empty capture list ===\n");
int lambda_local = 0;
// Can we modify the state inside the lambda body?
lambda_local++; // OK! 0->1
// local++; // Compile Err! local is not captured.
global++; // OK! 0->1
// What about functions that pass by copy?
printf("Pass by copy into fn:\n");
print_n(lambda_local); // 1
// print_n(local); // Compile Err! local not captured.
print_n(global); // 1
// What about functions that pass by reference?
printf("Pass by reference into fn:\n");
print_n_ref(lambda_local); // 1
// print_n_ref(local); // Compile Err!
print_n_ref(global); // 1
// What about functions that modify state but pass by copy?
printf("Pass by copy into fn, state modified in fn:\n");
add_one_print_n(lambda_local); // 2
print_n(lambda_local); // 1
// add_one_print_n(local); // err!
// print_n(local); // err!
add_one_print_n(global); // 2
print_n(global); // 1
printf("Pass by ref into fn, state modified in fn:\n");
add_one_print_n_ref(lambda_local); // 2
print_n_ref(lambda_local); // 2
print_n(lambda_local); // 2
// add_one_print_n_ref(local); // Compile err!
// print_n_ref(local); // Compile err!
add_one_print_n_ref(global); // 2
print_n_ref(global); // 2
print_n(global); // 2
});
default_t1.join(); // synchronize
// reset variables
global = 0; local = 0;
// DEFAULT CAPTURE BY COPY
// Variables being captured in the scope of main()
// are done by copy.
std::thread copy_default_t1([=](){
printf("=== Default copy capture list ===\n");
int lambda_local = 0;
// Can we modify the state inside the lambda body?
lambda_local++; // OK! 0->1
// local++; // Compile Err! local is captured as const (cannot be modified)
global++; // OK! 0->1
// What about functions that pass by copy?
printf("Pass by copy into fn:\n");
print_n(lambda_local); // 1
print_n(local); // 0
print_n(global); // 1
// What about functions that pass by reference?
printf("Pass by reference into fn:\n");
print_n_ref(lambda_local); // 1
// print_n_ref(local); // Compile Err! cannot pass int const as int &
print_n_ref(global); // 1
// What about functions that modify state but pass by copy?
printf("Pass by copy into fn, state modified in fn:\n");
add_one_print_n(lambda_local); // 2
print_n(lambda_local); // 1
add_one_print_n(local); // 1, OK - const local is copied
print_n(local); // 0
add_one_print_n(global); // 2
print_n(global); // 1
printf("Pass by ref into fn, state modified in fn:\n");
add_one_print_n_ref(lambda_local); // 2
print_n_ref(lambda_local); // 2
print_n(lambda_local); // 2
// add_one_print_n_ref(local); // Compile err! cannot pass int const as int &
// print_n_ref(local); // Compile err! cannot pass int const as int &
add_one_print_n_ref(global); // 2
print_n_ref(global); // 2
print_n(global); // 2
});
copy_default_t1.join();
// reset variables
global = 0; local = 0;
std::thread ref_default_t1([&](){
printf("=== Default ref capture list ===\n");
int lambda_local = 0;
// Can we modify the state inside the lambda body?
lambda_local++; // OK! 0->1
local++; // OK! 0->1, local is captured by ref
global++; // OK! 0->1
// What about functions that pass by copy?
printf("Pass by copy into fn:\n");
print_n(lambda_local); // 1
print_n(local); // 1
print_n(global); // 1
// What about functions that pass by reference?
printf("Pass by reference into fn:\n");
print_n_ref(lambda_local); // 1
print_n_ref(local); // 1
print_n_ref(global); // 1
// What about functions that modify state but pass by copy?
printf("Pass by copy into fn, state modified in fn:\n");
add_one_print_n(lambda_local); // 2
print_n(lambda_local); // 1
add_one_print_n(local); // 2
print_n(local); // 1
add_one_print_n(global); // 2
print_n(global); // 1
printf("Pass by ref into fn, state modified in fn:\n");
add_one_print_n_ref(lambda_local); // 2
print_n_ref(lambda_local); // 2
print_n(lambda_local); // 2
add_one_print_n_ref(local); // 2
print_n_ref(local); // 2
add_one_print_n_ref(global); // 2
print_n_ref(global); // 2
print_n(global); // 2
});
ref_default_t1.join();
} // main
|