Every now and then I come up with code like this:
// lazy lambda:
[&] {
// use references to a, b, c
}
// pedantic lambda
[&a, &b, &c] {
// use references to a, b, c
}
I'd like to know which of the lambdas is better in terms of performance and perhaps executable size according to the C++14 standard (or later) and your practical experience with compilers.
There is no difference in this example. The compiler will only capture variables that are explicitly referenced in the lambda, making the lazy capture equivalent to the explicit one.
A lazy capture is slightly more convenient. Changing the body of the lambda to use additional captured variables, or removing all references to a captured variable from an existing lambda, would normally also require updating an explicit capture list. With a lazy capture the compiler will do everything for you.
Excluding the trivial case where you explicitly capture something that isn't mentioned in the lambda at all, there are two corner cases where there might be a difference.
First, implicit captures generally do not capture entities that aren't odr-used (but see the next item for the exception to this). This includes, among other things, things mentioned in unevaluated operands such as those of decltype
and sizeof
, as well as certain const
and constexpr
local variables when used in certain contexts (consult [basic.def.odr] for the full set of rules):
void f(int){}
void f2(const int &) {}
void t() {
const int x = 8;
constexpr double y = 8;
const double z = 8;
auto g = []{ f(x); }; // OK
auto g1 = [=]{ f(x); }; // OK, does not capture x
// usually won't fire, though not guaranteed
static_assert(sizeof(g) == sizeof(g1), "!!");
auto g2 = []{ f(y); }; // OK
auto g3 = []{ f(z); }; // Error, must capture z
auto g4 = []{ f2(x); }; // Error, must capture x since it's odr-used
auto g5 = [=]{ f2(x); }; // OK, captures x
auto g6 = []{ f2(+x); }; // OK, doesn't odr-use x
auto g7 = []{ f2(y); }; // OK
}
If you write the capture list manually, it's possible that you'll capture more than you technically need to, because the rules governing what is or isn't odr-used are pretty complex.
Second, implicit captures in generic lambdas will capture things used in a dependent expression, even when they aren't necessarily odr-used, for sanity. Borrowing an example from the standard:
void f(int, const int (&)[2] = {}) { } // #1
void f(const int&, const int (&)[1]) { } // #2
void test() {
const int x = 17;
auto g2 = [=](auto a) {
int selector[sizeof(a) == 1 ? 1 : 2]{};
f(x, selector); // OK: is a dependent expression, so captures x
// Will actually odr-use x only if sizeof(a) == 1
};
}
However, you are not actually required to capture something that may-or-may-not-be-odr-used when writing a generic lambda; you are only required to capture it if you instantiate a specialization of the function call operator that odr-uses the thing. And so if you never call the resulting generic lambda in a way that would odr-use the thing at issue, then the implicit capture may capture more than the minimum you need. For example, this is allowed:
void test() {
const int x = 17;
auto g3 = [](auto a) { // no capture
int selector[sizeof(a) == 1 ? 1 : 2]{};
f(x, selector); // OK for now
};
}
as long as you don't call g3
with anything whose size is 1: g3(0)
is OK on typical systems; g3('\0')
isn't.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With