5

Should I make reference for a lambda to avoid copying it?

Will this code make a copy of the lambda:

auto myLambda = []() {/* do stuff */ }

and if yes, should I write it like this:

auto &myLambda = []() {/* do stuff */ }

P.S. Please sorry for a possibly newbie question, googled, but didn't find answer.

L. F.
  • 19,445
  • 8
  • 48
  • 82
Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • 3
    The second snippet doesn't compile, which might already give you a hint here. – lubgr Aug 29 '19 at 08:52
  • 1
    @lubgr Really? What's your compiler, is it GCC? I used msvc and c++17 flags and it compiles. – Nurbol Alpysbayev Aug 29 '19 at 08:53
  • 1
    @NurbolAlpysbayev you forgot the `const`? In any case, just use `auto myLambda = []() {}`. – andreee Aug 29 '19 at 08:54
  • 2
    not very different from `int i = 5;` no copy here – 463035818_is_not_an_ai Aug 29 '19 at 08:54
  • 1
    MSVS is known for allowing references to bind to rvalues, which is illegal in C++. `const auto&`, however, should work. Regardless, the first snippet does not copy anything. Copy-elision kicks in. What's more, if that lambda is stateless, it's size is 1 byte, which, in comparison to reference's (usually) 4 or 8 bytes, makes the first approach faster and preferable. – Fureeish Aug 29 '19 at 08:54
  • 1
    @formerlyknownas_463035818 but that's a literal. And I doubt lambdas considered as literals by compilers, hence the question.. – Nurbol Alpysbayev Aug 29 '19 at 08:55
  • 1
    What do you think a literal is and how does it differ in terms of generated assembly? Have you looked at the generated assembly of both approaches? – Fureeish Aug 29 '19 at 08:55
  • 3
    then consider `Foo f = Foo();` still no copy – 463035818_is_not_an_ai Aug 29 '19 at 08:56
  • 1
    @Fureeish So lambdas are literals to compilers? I just don't know, I thought maybe it still lives somewhere in memory, so we should make reference. But now I think I was wrong – Nurbol Alpysbayev Aug 29 '19 at 08:57
  • 1
    Why should it be a literal? It's a lambda. – andreee Aug 29 '19 at 08:58
  • 1
    No, they are not, but for some reason you assume some facts about literals. – Fureeish Aug 29 '19 at 08:58
  • 1
    Have a look at @formerlyknownas_463035818's second example (`Foo f = Foo();`) again; provide that foo class with custom default constructor and assignment operator. You'll notice that only the constructor will get called, f is constructed right in place. Same for the lambda. – Aconcagua Aug 29 '19 at 08:59
  • @Fureeish Just to finish the conversation... I mentioned literals because I've read that literals must not and can not be refererenced, because they don't occupy any space in memory. So I wondered if the same applies to lambdas or not. BTW thanks for the answer! (I've chosen the other one because it was made a bit earlier). Also, now I know, thanks to all you guys and especially L.F. that that's (literals, lambdas, non-reference function call results etc) called prvalue :) – Nurbol Alpysbayev Aug 29 '19 at 09:13
  • "*I've read that literals must not and can not be refererenced*" - well, `const int& ref = 5;` should and will compile. – Fureeish Aug 29 '19 at 09:16
  • @Fureeish Yep, I've read about that as well, but that's a constant and it's just a better alternative for `#define`. That's just what I've read, can't remember where exactly though... Anyway the question is solved, thanks! – Nurbol Alpysbayev Aug 29 '19 at 09:20
  • _"that's a constant and it's just a better alternative for `#define`"_ - no, [it's way more than that](https://stackoverflow.com/questions/42388077/constexpr-vs-macros) (note: referring to `constexpr` rather than `const`here, but the difference is explained in the answer as well). – andreee Aug 29 '19 at 09:31

2 Answers2

3

You should use the first one because the second one is ill-formed according to the C++ standard.

[expr.prim.lambda]/2:

A lambda-expression is a prvalue whose result object is called the closure object. [ Note: A closure object behaves like a function object. — end note ]

You cannot bind prvalues to lvalue references per [dcl.init.ref]/5. The fact that a particular compiler appears to accept it doesn't change anything. C++ is defined by the C++ standard.

In fact, the first one is guaranteed not to introduce a copy since C++17 — initializing from prvalues is guaranteed to construct the value in place. In practice, every decent compiler does the copy elision optimization, so you can safely assume that no copy is made.

L. F.
  • 19,445
  • 8
  • 48
  • 82
  • Hmm interesting, you made me read about what is prvalue (Yes, I am just learning c++), so prvalue is pure value, like a value that cannot change, and that includes lambdas. Thanks! – Nurbol Alpysbayev Aug 29 '19 at 09:07
  • 1
    @NurbolAlpysbayev Yes! If you change the "literals" in your comments to "prvalues", they make sense :) – L. F. Aug 29 '19 at 09:07
3

Will this code make a copy of the lambda:

auto myLambda = []() {/* do stuff */ }

No. Copy-initialisation kicks in and, regardless of the copy in the name, only one object is created.

and if yes, should I write it like this:

auto &myLambda = []() {/* do stuff */ }

Even if that was a copy, the above code is illegal in C++. Lvalue references cannot bind to rvalues (and a lambda expression is a prvalue, as per [expr.prim.lambda]/2:). MSVS introduces an extension which allows that - whether we like it or not. const auto& would make it work, but that would almost certainly be a pessimisation.

Fureeish
  • 12,533
  • 4
  • 32
  • 62