
Lambda expression comparison between C++11, C++14 and C++17 - ingve
http://maitesin.github.io//Lambda_comparison/
======
hellofunk
I love lambdas, but a lot of commenters are throwing around the word "closure"
here, and c++ lambdas are definitely not closures. You can capture outside
variables by value or by reference, but that value can expire before the
lambda runs if the reference no longer exists; in which case, you are in
trouble. Unlike a true closure (as in lisp or other languages), where the
closed-over value stays around.

If we talk only about c++ capture lists by value (i.e. [=]), then you could
make a case for a more appropriate use of the word "closure" but since many
lambdas do more than this, I think the distinction is necessary.

However, even in by-value captures, if you are capturing a pointer by value,
the issue remains. So, really it is not a good idea to think of lambdas as
closures in the functional sense typically used in other languages.

~~~
csl
I find that pointlessly pedantic. By the same measure, languages that don't
offer bignums don't offer integers. Even Lisps need to implement closures in
one way or another, and you may be surprised to see how they actually do it.

~~~
moonshinefe
I think you miss the point, the OP was mentioning the property that in most
languages, closures whose outer variables they are bringing into scope stay in
scope (even if the outer function ends). In C++ they expire (is what I got
from his comment, anyway).

~~~
gpderetta
Closed-by-value variables (I.e. the default) in C++ don't expire. Referenced
or pointed-to object might, but this is completely consistent with the rest of
the language.

Remember that C++ is a by value language. Pointers are explicit, fist class
and distinct from the pointed to object.

References are werid, though.

------
aurelian15
Nice overview. Just a small correction: the smallest possible lambda is

    
    
        []{}
    

and not

    
    
        [](){}
    

as stated in the article. The parameter list is optional.

~~~
hellofunk
>The parameter list is optional.

To avoid confusion, only an empty parameter list is optional. You can't omit
the parameter list if you take args, unlike lambda shortcuts in languages like
clojure which allow you to implicitly refer to arguments inside the body using
a universal variable name, and therefore omit the parameter list for all
lambdas. That possibility does not exist in c++.

------
hendzen
Another mildly obscure feature of lambdas is the ability to capture a variadic
number of parameters.

Example (slightly contrived):

    
    
      #include <future>
      #include <iostream>
    
      template<typename... Args>
      void log(Args&&... args) {
          (std::cout <<  ...  << args) << std::endl;
      }
    
      template<typename... Args>
      std::future<void> log_async(Args&&... args) {
        return std::async(std::launch::async, [args...] { log(args...); });
      }
    
      int main()
      {
          auto f = log_async(1, 2, 3);
          f.wait();
      }

~~~
groovy2shoes
For those of us still stuck on C++98 at work, would you mind explaining what's
going on here? In particular, I can't figure out why the ellipsis is so
separated from `args` here:

    
    
            (std::cout << ... << args) << std::endl;
    

That looks like some black magic to me. The rest makes sense, I think.

~~~
hendzen
[http://en.cppreference.com/w/cpp/language/fold](http://en.cppreference.com/w/cpp/language/fold)

~~~
groovy2shoes
Neat! Thank you :)

------
typon
Taking a concept such as a lambda function and making it look this ugly...this
is why I hate C++. I wish I wasn't forced to program it every day.

~~~
hendzen
How is it ugly? The capture list is a necessary complexity in a language with
manual memory management.

~~~
partisan
The syntax is not the prettiest, but it is legible once you understand what
[](){} means.

In C#, there is no such thing, but there is a part of me that wishes we had
such a thing. I like the ability explicitly state what variables are being
captured.

~~~
rtpg
I'm not 100% sure, but C#'s compiler should automatically capture what you
need (and leave out the rest).

I think the primary need for manual declaration is because in C++ you need to
differentiate between pass by copy semantics and pass by reference semantics.

~~~
masklinn
> I think the primary need for manual declaration is because in C++ you need
> to differentiate between pass by copy semantics and pass by reference
> semantics.

That's not actually a _need_ , C++ includes [=] and [&] (capture everything by
value or by reference). You can get a mix by creating references outside the
body then capturing the environment by value (capturing the references by
value and thus getting references).

On the one hand it has a bit more syntactic overhead (you have to take and
declare a bunch of references before creating the closure), on the other hand
there's less irregularity to the language, and bindings mean the same thing in
and out of the closure.

FWIW that's what Rust does[0], though it may help that Rust's blocks are
"statement expressions", some constructs would probably be unwieldy without
that.

[0] the default corresponds to C++'s [&] (capture by ref), and a "move
closure" switches to [=] instead

------
jaytaylor
This is the first time I've looked at C++ lambdas. They appear magnificently
powerful and also like another pile of easy ways to get completely screwed up.

Ah well, that's just the C++ way I suppose.

Makes me glad for Rust, that's for sure!

~~~
jguegant
Here take your rustwin point!

I really like the explicit capture of C++'s lambdas more than the implicit one
in most other languages (C#, Java, Python...) where you easily ends-up with a
closure not referencing the expected variable. See:
[https://blogs.msdn.microsoft.com/ericlippert/2009/11/12/clos...](https://blogs.msdn.microsoft.com/ericlippert/2009/11/12/closing-
over-the-loop-variable-considered-harmful/)

~~~
netheril96
I was burned by the same thing in Javascript.

"Explicit is better than implicit". Therefore, I agree with you that explicit
closure list, with the ability to copy _and_ reference captured variables, is
actually what C++ does right, not wrong.

~~~
groovy2shoes
The explicit capture list is only necessary in C++ because memory ownership
and lifetimes are managed by the programmer in C++. Compare that to a garbage-
collected language like Scheme or C#: when the implementation can figure out
where memory needs to be freed and ensures you can't use-after-free, it frees
the programmer from thinking about ownership (but not necessarily lifetimes:
you can still wind up with memory leaks in GC'd languages if you're not
careful to let go of references you no longer need). As mentioned elsewhere in
this thread, Rust also offers the same level of explicit control without
capture lists (though I'm on the fence about which way I prefer).

My point is that in languages with automatic memory management, explicit
capture lists don't make much sense because the programmer is not tasked with
managing memory and can safely capture references all the time. There's no
need to ask oneself, "Do I own this pointed-to memory? Do I need to worry
about it being freed before this closure? Should I make a copy?", etc. This is
because, in a sense, the garbage collector itself owns the memory, but checks
to make sure nothing else can use it anymore before it frees it.

~~~
netheril96
You only talk about the memory management part and I guess most language
designers think the same. What you and they fail to account for is that
explicit capture list can reduce logical bugs.

For one, if I were allowed to explicitly capture the counter variable by copy,
the surprising behavior mentioned above would never occur. In languages with
mutability, the ability to make some part immutable is a virtue.

For two, in languages without explicit variable declaration, which variable is
defined where quickly becomes murky when you have implicit capture. I have so
many frustrations where the inner `i` variable clashes with the outer `i` in
Python. Yes, I could just use a different name, but naming is hard, and with a
new scope I should be able to reuse the name. That is almost the whole point
of opening a new scope!

For three, in Javascript where closures are everywhere due to the amount of
callbacks, the reference graph is just impossible to analyze. A closure may
closes over another closure which closes over an object with a reference to
the original closure. An explicit capture list makes the programmer think, and
ease the job of anyone who tries to spot memory leaks from the source code.
(But I guess that is just not the Javascript style, as they are so fond of
never letting the programmers know about their mistakes. At least in C++ we
trade that for speed. I don't know what Javascript trades that for.)

~~~
groovy2shoes
> You only talk about the memory management part and I guess most language
> designers think the same. What you and they fail to account for is that
> explicit capture list can reduce logical bugs.

I suppose, as a language designer, I tend to think that the more I do
automatically, the more I ease the programmer's burden. However, as you point
out, that's not always true. That said, my point wasn't (isn't?) that explicit
capture is only a good idea sans automatic memory management (it may well be
-- you've certainly given me some food for thought here), but rather that it's
only _necessary_ in that case, and I think that point still stands.

> For one, if I were allowed to explicitly capture the counter variable by
> copy, the surprising behavior mentioned above would never occur.

That's a failure of language design and I don't think the proper solution is
to force explicit capture on closure creation (also note that you need _more_
than just explicit capture because to prevent such an error, you need the
ability to specify that the "captured" variable ought to be copied rather than
actually captured). I think the proper solution to that problem is the one
that the C# team went with: limit the scope of iteration control variables to
the iterated block. This is typically what programmers used to block-
structured languages would expect, anyway, unless the variable were clearly
declared outside the scope of the iteration.

> In languages with mutability, the ability to make some part immutable is a
> virtue.

That's an orthogonal issue, and can be done in many other (and more general)
ways.

> For two, in languages without explicit variable declaration, which variable
> is defined where quickly becomes murky when you have implicit capture. I
> have so many frustrations where the inner `i` variable clashes with the
> outer `i` in Python. Yes, I could just use a different name, but naming is
> hard, and with a new scope I should be able to reuse the name. That is
> almost the whole point of opening a new scope!

You're right: that _is_ the point of opening a new scope! That sounds like a
flaw in Python's design and could be remedied by making variable definition
syntax different from assignment syntax. Consider Lua with its `local` syntax,
C and kin with their type annotations, the Lisps with their completely
separate forms for variable definition and assignment, and so on. There's also
the Tcl strategy of "it's a definition unless it was imported into this scope
with `global` or `upval`; otherwise it's an assignment".

> For three, in Javascript where closures are everywhere due to the amount of
> callbacks, the reference graph is just impossible to analyze. A closure may
> closes over another closure which closes over an object with a reference to
> the original closure. An explicit capture list makes the programmer think,
> and ease the job of anyone who tries to spot memory leaks from the source
> code. (But I guess that is just not the Javascript style, as they are so
> fond of never letting the programmers know about their mistakes. At least in
> C++ we trade that for speed. I don't know what Javascript trades that for.)

JavaScript is a shitty language to begin with, and fixing it wouldn't be as
simple as fixing C# or Python... You make a good point here, but I still think
that better tooling for data-flow analysis is a more attractive choice than a
compulsory explicit capture list. On the flip side, an optional capture list
could be a good compromise.

~~~
netheril96
> On the flip side, an optional capture list could be a good compromise.

That is exactly what I am thinking about. Or, rather, what C++ has done right:
You can let the compiler infer what to capture, like [=] or [&], or you can
explicitly list the variables to capture.

> you need the ability to specify that the "captured" variable ought to be
> copied rather than actually captured

Yes, that is what I am talking about, and again, what C++ has done right. Most
other languages give you no choice whether the capture is by copy or by
reference.

~~~
groovy2shoes
Ah, then I see we're in agreement :)

> Most other languages give you no choice whether the capture is by copy or by
> reference.

That's because in languages that have traditionally had GC (i.e., languages in
the Lisp tradition or in the ML tradition), the distinction didn't matter.
Those languages did not "suffer" from a value/reference dichotomy (e.g., in
Scheme, you're literally capturing the _variable_ rather than a copy or
reference to the value stored within -- under the hood, that variable might
always store a reference for convenience, or it might store a value for
performance, but it doesn't matter as it's strictly an implementation detail).

I'm glad that the C++ committee didn't just dump closures into the language
without considering this sort of interaction with other aspects of the
language. Without the capture lists, closures in C++ have the potential to
_really_ suck. That the explicit capture lists even exist is evidence that
they've carefully considered how the new features are going to play with
existing characteristics of C++. Kudos to them for that!

------
oolongCat
A question people who use C++ regularly, is C++ becoming easier to read and
code?

~~~
MereInterest
Absolutely, and without a doubt.

* `unique_ptr` as a local variable. Before C++11, I needed to either (a) define a holder class for anything that should be deleted at the end of a scope or (b) delete it manually and pray that there isn't an exception thrown. Now, I can just declare it, and trust the destructor to clean up after me.

* `unique_ptr` as a return value. Previously, if a function returns a pointer, there was no way on knowing who was responsible for calling `delete`. Now, I can clearly indicate intent. `unique_ptr` means that the caller now owns the object, while C-style pointer or reference means that the callee still owns the object.

* With lambda statements, I can call `std::sort` in-place, with the sorting criteria immediately visible. Previously, I would need to define a function elsewhere in the code, obscuring what may be a simple `a.param < b.param`.

* With range-based for loops, I can loop over any container without needing the very long `std::vector<MyClassName>::iterator` declaration.

* `= delete` to remove an automatically generated method, such as copy constructors. Previously, you would declare that method to be private, then never make an implementation of it. `= delete` shows your intent much more clearly.

* `static_assert`, so that you can bail out of templates earlier, and with reasonable error messages.

* Variadic templates. These aren't needed in 99% of cases, but they are incredibly useful when designing libraries.

* `std::thread` No more messing around with different thread libraries depending on which platform you are on.

~~~
jwilk
Re unique_ptr as a local variable: in C++98 you can use auto_ptr.

~~~
MereInterest
True, I didn't mention it, because it has its own issues. The move-on-copy
semantics of auto_ptr makes it incompatible with std containers, and makes for
some rather unexpected behavior.

------
Kristine1975
Clang's C++1z support:
[http://clang.llvm.org/cxx_status.html](http://clang.llvm.org/cxx_status.html)
(scroll down a bit)

And gcc's: [https://gcc.gnu.org/projects/cxx-
status.html#cxx1z](https://gcc.gnu.org/projects/cxx-status.html#cxx1z)

Unfortunately neither supports constexpr lambdas at the moment.

------
makecheck
One aspect of C++ lambdas that I really don’t like is the visual confusion
caused by allowing "return", since at a glance this seems to affect the
_parent_ function. I have already found myself adding comments inside lambdas
like "return x; // return-from-lambda" to make sure that I see what is really
happening. Python by contrast does two things better: Python makes it really
hard to write long expressions as lambdas, and no "return" is used in a Python
"lambda". Of course, Python also allows "def" inside a "def" as a convenient
way to write longer one-time functions.

I also found that while I could use C++ lambdas for things like iteration,
e.g. "object->forEachThing([](Thing const& t, bool& stop){ ... })", this makes
the keyword problem worse. In this type of call, if I want to implement
something that is _logically_ like a "break" or "continue" of the loop, it has
to use the "return" keyword (from the lambda only) with special conditions
attached such as a "bool" variable to request the break. And that is confusing
to read, even though conceptually it is similar to the Objective-C NSArray
"enumerateObjectsUsingBlock:" that takes a similar approach (in that the block
takes a "stop" argument).

------
forrestthewoods
Not a bad article. I wish the first example wasn't so complicated. C++ lambda
syntax is pretty gross. The initial breakdown is great. But why use a
std::vector and std::transform in the first real example? Stick to integer
addition. Keep things simple.

------
vmorgulis
There is another interesting feature in C++17 for lambdas:

Possibility to cast a lambda to a function pointer.

It will become possible to store a lambda as a struct/member that could bind
to "this" (like in Javascript).

~~~
hellofunk
I assume this is only for stateless lambdas, that have not captured anything?
Because otherwise a function pointer would not be enough, right?

Currently, any stateless lambda can decompose into a function pointer and be
passed to any function that expects a function pointer, right?

Are you saying that ++17 has augmented this, and if so can you provide more
details or a reference (or an example) as I'm quite curious to know more.

~~~
vmorgulis
[https://isocpp.org/files/papers/p0018r3.html](https://isocpp.org/files/papers/p0018r3.html)

It's the best reference I found. The paper only talks of capturing "*this" by
value (as in the original post of the topic).

I think I read that in a draft about coroutines. The idea was to capture
"this" by reference and convert the lambda to a function pointer to make it
movable.

It would useful for delegates or signals too.

------
meshko
Nice. I am a bit confused -- before c++17 it is impossible to capture this by
copy; is it possible to capture other objects by copy? seems like it is?

~~~
Kristine1975
Yes, it is. Just write:

    
    
      [a](...){ ... }
    

and the variable "a" will be captured by copy.

