1

With lambdas, in C++11, we can have a default capture mode set to by-value/by-ref, e.g. [=]/[&], optionally followed by explicit captures, by-ref/by-value, for some variables, e.g. [=,&this_is_by_ref] or [&,this_is_by_value].

In C++14, we can also have explicit captures by-move, e.g. [y = std::move(x)].

In Effective Modern C++, Item 32, 3rd paragraph, I read

The one thing you can't express with an init capture is a default capture mode, […]

What is the author most likely referring to?

We already have a way to capture all variables we need by copy or by reference. Why would we want to express that with the x = y form?

Maybe the author is referring only to the a "capture by move default"? Something which would work like [x = std::move(x), y = std::move(y), …] with all variables used in the body listed?

Enlico
  • 23,259
  • 6
  • 48
  • 102

3 Answers3

4

The paragraph states that there is no possibility to combine init capture with a default capture mode ([&] or [=]) and there is no new default capture mode for move semantics.

We already have a way to capture all variables we need by copy or by reference. Why would we want to express that with the x = y form?

Your thought is correct. Combining init capture with default capture would have the same behavior as the old way of default capture. Maybe that is the reason why it is omitted. However init-capture can do new things, that could not be done before. (like assigning a value to a new variable)

Maybe the author is referring only to the a "capture by move default"? Something which would work like [x = std::move(x), y = std::move(y), …] with all variables used in the body listed?

That is true too.

To better understand understand what the new possibilities are with init capture i give you this little example. Lets suppose you want to fill a vector with the values from 0 to n. The old way of doing this with the lambda would be:

std::vector<int> vec;
int i = 0;
std::generate_n(std::back_inserter(vec), n, [i]()mutable{
    return i++;
});

And now with init capture you can value initialise your counting variable in the lambda capture:

std::vector<int> vec;
std::generate_n(std::back_inserter(vec), n, [i=0]()mutable{
    return i++;
});

That was not possible before and is useful in many places.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
OutOfBound
  • 1,914
  • 14
  • 31
2

"init capture" is [z = y] style of capture.

The only kind of capture you cannot express with this style of capture is default capture mode. Ie:

[&, z=y]{ /* some code */ }

here & is using the old-style capture syntax. It expresses that we have a default capture mode. z=y is the new-style "init capture".

There is no way to use "init capture" to express the default capture mode.

Anything else you do with the old style capture syntax, you can duplicate with init capture syntax.

Ie:

[x=x]{ /* some code */ }

is another way of expressing

[x]{ /* some code */ }

and

[&x=x]{ /* some code */ }

is another way of expressing

[&x]{ /* some code */ }

now this isn't all that deep.

But if your argument is "don't use the old capture syntax", you should prove you don't need it anymore. And you don't need the old capture syntax, except for when you are defining a default capture mode.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

The answer by @OutOfBound is already good. I will only express a similar ideas from a different angle.

Scott Meyers in the first few paragraphs of item 32 notices that:

  • The capture syntax introduced in C++11 was seen as unsatisfactory even at the moment the standard was adopted.
  • The reason for this was the absence of means to capture using move semantics (or, for example, by const reference), except, perhaps, with some awkward, obscure workarounds.
  • The standard committee could have improved the capabilities of the C++11 syntax by simply expanding it (I guess, for example by introducing [&&, &&x]), but eventually chose a different, more general approach: they introduced a new syntax ("C++14 syntax").
  • Scott then notices that in principle you could ALMOST completely forget about the old, C++11 syntax and use exclusively C++14 syntax (the one with the capture initialization).
  • The only thing that you can really do only with the old syntax is to define the default capture mode.
  • However, Scott discourages C++ programmers from using the default capture mode anyway (Item 31).
  • So the meaning of the sentence is: "In practice a C++ programmer (or the C++ language) does not need the old syntax, I wish the new syntax had been adopted already in C++11".

Notice also that the new capture syntax is so flexible that it allows not only for expressing your intention of using move semantics, but also for const references (Scott omits this latter point) and mutable lambdas.

Enlico
  • 23,259
  • 6
  • 48
  • 102
zkoza
  • 2,644
  • 3
  • 16
  • 24
  • What is a capture by `const&` like? +1, btw. – Enlico Jan 06 '21 at 15:32
  • `[&cr = std::as_const(x)]`, see https://en.cppreference.com/w/cpp/language/lambda; also: https://stackoverflow.com/questions/3772867/lambda-capture-as-const-reference – zkoza Jan 06 '21 at 16:07
  • `std::as_const` comes with C++17, so Scott Meyers was certainly not allunding to it. Anyway, good to know this usage. – Enlico Jan 06 '21 at 16:35
  • Yes, it comes with *the standard library* of c++17, but could have been implemented already in C++11. I think most people can live without const references in lambdas because lambdas are usually short and used in constrained environments, and hence their manual "management" does not hurt. – zkoza Jan 08 '21 at 09:45
  • Mmm... It happens frequently that I write a fairly long lambda. And I write a lambda instead of a function because I cannot pass the latter around. And I write a lambda instead of an hand-crafted `struct`-with-`operator()` because it's clear to the reader that the former is meant to be called (whereas they have to skim through the latter for `operator()` to understand it's a callable). However, this is going OT, sorry. – Enlico Jan 08 '21 at 09:49