In February, I announced that I'd be offering a new training seminar in Oslo, London, and Stuttgart. The seminar was Effective C++11 Programming. Because I was still working on the material for the seminar, I indicated that the course description I posted was preliminary.
A lot has happened since then. First, I finished my materials for the training course. Second, a draft version of C++14 was published. Third, I revised my materials to incorporate parts of C++14 that are particularly relevant and that seem likely to remain stable as C++14 is finalized. And fourth, I changed the name of the seminar to Effective C++11/14 Programming. The course descriptions for the seminars I'll hold in Oslo and London have now been updated to reflect the change in course title and the no-longer-tentative list of topics. The links below will take you to these updated pages:
The information for the presentation in Stuttgart has not yet been updated. That should happen soon, but there may be further refinements to that description later this summer, because the version of the course I'll present there will benefit from experience I get delivering it in Oslo (world debut!) and London.
I hope to see you in Oslo, London, or Stuttgart to talk about how to make effective use of C++11 and C++14.
Scott
Friday, May 31, 2013
Wednesday, May 22, 2013
Lambdas vs. Closures
In recent days, I've twice found myself explaining the difference between lambdas and closures in C++11, so I figured it was time to write it up.
The term "lambda" is short for lambda expression, and a lambda is just that: an expression. As such, it exists only in a program's source code. A lambda does not exist at runtime.
The runtime effect of a lambda expression is the generation of an object. Such objects are known as closures.
Given
the blue expression to the right of the "=" is the lambda expression (i.e., "the lambda"), and the runtime object created by that expression is the closure.
You could be forgiven for thinking that, in this example,
The distinction between a lambda and the corresponding closure is precisely equivalent to the distinction between a class and an instance of the class. A class exists only in source code; it doesn't exist at runtime. What exists at runtime are objects of the class type. Closures are to lambdas as objects are to classes. This should not be a surprise, because each lambda expression causes a unique class to be generated (during compilation) and also causes an object of that class type--a closure--to be created (at runtime).
Scott
PS - I noted above that a closure is typically destroyed at the end of the statement in which it is created. The exception to this rule is when you bind the closure to a reference. The simplest way to do that is to employ a universal reference,
but binding it to an lvalue-reference-to-
The term "lambda" is short for lambda expression, and a lambda is just that: an expression. As such, it exists only in a program's source code. A lambda does not exist at runtime.
The runtime effect of a lambda expression is the generation of an object. Such objects are known as closures.
Given
auto f = [&](int x, int y) { return fudgeFactor * (x + y); };
the blue expression to the right of the "=" is the lambda expression (i.e., "the lambda"), and the runtime object created by that expression is the closure.
You could be forgiven for thinking that, in this example,
f
was the closure, but it's not. f
is a copy of the closure. The process of copying the closure into f
may be optimized into a move (whether it is depends on the types captured by the lambda), but that doesn't change the fact that f
itself is not the closure. The actual closure object is a temporary that's typically destroyed at the end of the statement.The distinction between a lambda and the corresponding closure is precisely equivalent to the distinction between a class and an instance of the class. A class exists only in source code; it doesn't exist at runtime. What exists at runtime are objects of the class type. Closures are to lambdas as objects are to classes. This should not be a surprise, because each lambda expression causes a unique class to be generated (during compilation) and also causes an object of that class type--a closure--to be created (at runtime).
Scott
PS - I noted above that a closure is typically destroyed at the end of the statement in which it is created. The exception to this rule is when you bind the closure to a reference. The simplest way to do that is to employ a universal reference,
auto&& rrefToClosure = [&](int x, int y) { return fudgeFactor * (x + y); };
but binding it to an lvalue-reference-to-
const
will also work: const auto& lrefToConstToClosure = [&](int x, int y) { return fudgeFactor * (x + y); };
Monday, May 6, 2013
C++14 Lambdas and Perfect Forwarding
So the joke's on me, I guess.
In my discussion of
Let me explain.
The recently-adopted C++14 CD includes beefy additions to lambda capabilities, including the support for polymorphic lambdas that Herb Sutter can't help but mention I've been whining about for years. This means that in C++14, we now have the expressive power that the Boost Lambda library has been offering since 2002. Ahem. But C++14 goes further, supporting also variadic lambdas, generalized captures (including capture-by-move), and, of particular relevance to this post, support for perfect forwarding.
Suppose we want to write a C++14 lambda that takes a parameter and perfect-forwards it to some function
The solution takes advantage of two observations. First, the type-deduction rules for
Which brings us to observation number two. As I noted near the beginning of this post,
Scott
In my discussion of
std::move
vs. std::forward
, I explained that when you call std::forward
, the expectation is that you'll pass a type consistent with the rules for template type deduction, meaning (1) an lvalue reference type for lvalues and (2) a non-reference type for rvalues. I added,
If you decide to be a smart aleck and write [code passing an rvalue reference type], the reference-collapsing rules will see that you get the same behavior as [you would passing a non-reference type]
, but with any luck, your team lead will shift you to development in
straight C, where you'll have to content yourself with writing bizarre macros.
Well. As I said, the joke seems to be on me, because the standardization commitee apparently consists largely of smart alecks.Let me explain.
The recently-adopted C++14 CD includes beefy additions to lambda capabilities, including the support for polymorphic lambdas that Herb Sutter can't help but mention I've been whining about for years. This means that in C++14, we now have the expressive power that the Boost Lambda library has been offering since 2002. Ahem. But C++14 goes further, supporting also variadic lambdas, generalized captures (including capture-by-move), and, of particular relevance to this post, support for perfect forwarding.
Suppose we want to write a C++14 lambda that takes a parameter and perfect-forwards it to some function
f
:auto forwardingLambda = [](auto&& param) { /* perfect-forward param to f */ };Writing the perfect-forwarding call is easy, but it's probably not obvious how. The normal way to perfect-forward something it to use
std::forward
, so we'd expect to write essentially this: auto forwardingLambda = [](auto&& param) { f(std::forward<T>(param)); };But, uh oh, there's no
T
to pass to std::forward
. (In the class generated from the lambda expression, there is, but inside the lambda itself, there's no type for param
.) So what do we pass to std::forward
? We can hardly pass auto
. (Consider what would happen if we had a lambda taking multiple parameters, each of type auto
and each of which we wanted to forward. In that case, each std::forward<auto>
would be ambiguous: which auto
should std::forward
use?)The solution takes advantage of two observations. First, the type-deduction rules for
auto
in lambdas are the same as for templates. This means that if an lvalue argument is passed to the lambda, param
's type will be an lvalue reference--exactly what we need for std::forward
. If an rvalue argument is passed, its type will be an rvalue reference. For such parameters, we can recover the type to pass to std::forward
by stripping it of its reference-ness. We could thus write forwardingLambda
like this:auto forwardingLambda = [](auto&& param) { f(std::forward<std::conditional<std::is_rvalue_reference<decltype(param)>::value, std::remove_reference<decltype(param)>::type, decltype(param)>::type>(param)); };At least I think we could. I don't have a C++14 compiler to try it with, and, anyway, it's too gross to waste time on. It would be sad, indeed, if this is what the standardization committee expected us to do to effect perfect forwarding inside its spiffy new C++14 lambdas. Fortunately, it doesn't.
Which brings us to observation number two. As I noted near the beginning of this post,
If you decide to be a smart aleck and write [code passing an rvalue reference type to std::forward], the reference-collapsing rules will see that you get the same behavior as [you would passing a non-reference type]
.
That means that if param
's type is an rvalue reference, there is no need to strip off its reference-ocity. Instead, you can smart aleck your way to success by simply passing that type directly to std::forward
. Like so: auto forwardingLambda = [](auto&& param) { f(std::forward<decltype(param)>(param)); };Frankly, this is more verbose than I'd prefer. One could imagine a world where you could say something like this:
auto forwardingLambda = [](<T1>&& param1, <T2>&& param2) { f(std::forward<T1>(param1), std::forward<T2>(param2)); };But that's not the world we live in, and given that C++14 gives us polymorphic lambdas, variadic lambdas, and move-enabled lambdas, I'm not going to complain about the world of C++14 lambdas. Except possibly to Herb :-)
Scott
Shared State from std::async remains special
In an earlier post, I pointed out that, contrary to the way things are generally described, it's not the futures returned from
Scott
std::async
that are special, it's the shared state they refer to that is. In the comments that followed that post, it was pointed out that this could change in C++14, but the proposal to that effect was rejected at the standardization committee meeting last month. As Anthony Williams put it in his blog post,Herb Sutter's late paper on the behaviour of the destructor ofstd::future
(N3630) was up next. This is a highly conterversial topic, and yielded much discussion. The crux of the matter is that as currently specified the destructor ofstd::future
blocks if it came from an invocation ofstd::async
, the asynchronous function was run on a separate thread (with thestd::launch::async
policy), and that thread has not yet finished.
[...]
Much of the discussion focused on the potential for breaking existing code, and ways of preventing this. The proposal eventually morphed into a new paper (N3637) which created 2 new types of future:C++14 now has CD ("committee draft") status, but that doesn't mean things can't change. A member of the committee emailed me as follows:waiting_future
andshared_waiting_future
.std::async
would then be changed to return awaiting_future
instead of afuture
. Existing code that compiled unchanged would then keep the existing behaviour; code that changed behaviour would fail to compile. Though the change required to get the desired behaviour would not be extensive, the feeling in the full committee was that this breakage would be too extensive, and the paper was also voted down in full committee.
[The] paper on changing [the behavior of futures referring to shared state from std::async
] was rejected, after a LOT of discussion. The discussion has continued on the reflector, and we may get a NB comment on the C++14 draft about it, but for now there is no change.
My impression is that many committee-watchers had considered a change in the specification for std::async
to be a sure thing, but, as I wrote in yet another blog post, the committee tends to be quite conservative about the possibility of breaking existing code. At this point, that looks to be the line they're going to follow as regards the behavior of (the shared state corresponding to) futures produced by std::async
.Scott
Subscribe to:
Posts (Atom)