Two things in N2960's lambda specification surprised me. First, captures by value of const/volatile objects seem to yield copies with the same cv qualifiers as the original. This means that if the original is const, the copy is const, too. That has the interesting implication that mutable lambdas can't modify the copies of const data in the closure type, because mutable lambdas don't yield operator()s in the closure type that are mutable (*). So:
void example1() { const int x = 5; auto f1 = [=]{ ++x; }; // error, f1's copy of x is const auto f2 = [=]() mutable { ++x; }; // still an error, x is const, dammit }
Second, the behavior of by-reference capture doesn't seem to be done in terms of reference data members in the closure type. Rather, it seems to be done in terms of name lookup inside the closure expression. In other words,
void example2() { int x = 5; auto f = [&]{ ++x; }; // f's type isn't specified to have a } // data member corresponding to x
Is my understanding correct? (I'm not objecting to either of the things above, I'm just trying to make sure I understand what I'm reading.)
Thanks,
Scott
(*) Or, as I'm sure to explain it some day, "There's no such thing as a const lambda, but operator()s generated from lambdas are const. There are mutable lambdas, but the operator()s generated from them aren't mutable, they're just not const. Any questions?"
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On 2009-11-03, Scott Meyers <use...@aristeia.com> wrote:
> Second, the behavior of by-reference capture doesn't seem to be done in terms > of reference data members in the closure type. Rather, it seems to be done > in terms of name lookup inside the closure expression.
What about this:
{ int x = 3, &y = x; ... }
Here, x and y are indistinguishable. They are aliases for the same memory location. You can't write code in the place of ... which can tell that x is somehow original and y is a derived reference. Therefore, for all intents and purposes, y is just another name for the same thing as x; it is a name reference.
Yet, it's not incorrect to describe y as a reference, either.
``Reference'' does not imply ``data member of a reference kind'', distinct from ``name lookup''. C++ references really can /do/ name lookup in some situations.
> In other words,
> void example2() > { > int x = 5; > auto f = [&]{ ++x; }; // f's type isn't specified to have a > } // data member corresponding to x
By data member, do you mean that f.x is a valid expression, so that f in fact has a kind of ``backing structure'' type? Such that perhaps we can even fiddle with closures using expressions like f.x = 42?
How would that work if f is passed to some other scope, where there isn't any static type information about its ``backing structure'' type?
Do we add a symbol table to each closure, using which you can do name lookups at run-time?
(Lisp has symbols, and yet doesn't provide this kind of reflection over closures, for good reasons! Though implementations provide it as debug info; if you are at a debug breakpoint in the middle of a closure, it's nice to be able to see the values of variables by name.)
Other than that, visible data members on closures are a non-starter on many fronts.
The x lambda [&]{ ++x; } can be understood to be an alias reference of the same kind as the y in the x and y example: it's a reference, but at the same time indistinguishable from a name lookup.
These C++ closures are a joke anyway; they are not first class. In Lisp speak, we would say they are ``downward funargs only''. If the function terminates, the so-called ``closure'' becomes toast. A better word for this object would be ``fissure''. :)
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
These C++ closures are a joke anyway; they are not first class.
That's false. They can be assigned, stored, composed, and called repeatedly.
In Lisp speak, we would say they are ``downward funargs only''.
> If the function terminates, the so-called ``closure'' becomes toast.
That's fine, and is exactly what one expects of C++. C++ isn't trying to be Lisp, and there is no chance in C++ of redefining the language to allow automatic variables to outlast the exiting of their scope.
Nevertheless, lambdas will be incredibly useful.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 3, 7:08 pm, Scott Meyers <use...@aristeia.com> wrote:
> Second, the behavior of by-reference capture doesn't seem to be done in terms of > reference data members in the closure type. Rather, it seems to be done in > terms of name lookup inside the closure expression. In other words,
> void example2() > { > int x = 5; > auto f = [&]{ ++x; }; // f's type isn't specified to have a > } // data member corresponding to x
There is an important reason for this: the reference_closure type basically requires lambdas that capture everything by reference to be implemented as a pair of a function pointer and a scope pointer*. So there is no data member corresponding to x above. There's a data member pointing to the start of the stack frame of the invocation of example2 where the lambda was created, and the access to x happens through an offset to this pointer.
* A compiler could perhaps come up with an alternate implementation, but there's no motivation to do so.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
> These C++ closures are a joke anyway; they are not first class. In Lisp speak,
Thank you, that's very helpful. I am sure people appreciate that feedback.
> we would say they are ``downward funargs only''. If the function terminates, > the so-called ``closure'' becomes toast. A better word for this object
You can do capture-by-reference for objects stored on the free store. You can also capture-by-value smart pointers. There are ways to do closures that do not become "toast". Capturing objects by value makes it easy to capture snapshots of values. You can have closures that are useful for a lot of things, without mandating garbage collection in the language. C++ lambdas are also useful for certain cases that we currently have to use preprocessor macros for. They may not be identical to lisp closures, but it's perhaps not possible to have closures in C++ that would be identical to closures in other languages. Some compromises are necessary, but that doesn't IMHO make C++ lambdas worthless, there are many things you can do with them that are very useful.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 6, 12:28 am, Hyman Rosen <hyro...@mail.com> wrote:
> Kaz Kylheku wrote: > These C++ closures are a joke anyway; they are not first class. > That's false. They can be assigned, stored, composed, and called > repeatedly.
Other parts of that are correct, but the assignment is not. [expr.prim.lambda]/p18 states that "The closure type associated with a lambda-expression has a deleted default constructor and a deleted copy assignment operator."
The copy assignment is deleted so you can't assign lambdas. You can copy construct them, though. Unfortunately I don't recall hearing any explanation as to why they can't be assigned.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 3, 8:08 pm, Scott Meyers <use...@aristeia.com> wrote:
> void example1() > { > const int x = 5; > auto f1 = [=]{ ++x; }; // error, f1's copy of x is const > auto f2 = [=]() mutable { ++x; }; // still an error, x is const, dammit > }
That is correct AFAIK. It would be beneficial to see whether that is a problem, we corrected the reach of lambda in order to be able to port functional code from other languages to c++, so if there are cases (or existing algorithms) where modification of copies in a lambda is necessary, we may want to revisit that design decision.
> Second, the behavior of by-reference capture doesn't seem to be done in terms of > reference data members in the closure type. Rather, it seems to be done in > terms of name lookup inside the closure expression. In other words,
I don't think that's the case. [ext.prim.lambda]/p15 says "It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.", and p22 says "[ Note: If an entity is implicitly or explicitly captured by reference, invoking the function call operator of the corresponding lambda-expression after the lifetime of the entity has ended is likely to result in undefined behavior. — end note ]"
I believe the text in p15 attempts to allow for implementations that store a stack frame instead of storing the references individually. Now that the reach of lambda has been fixed, this text may need to be clarified, because it's possible to capture eg. function arguments, and those may be references to objects that don't live on the stack and thus storing a stack frame would not work. Still, for cases where a stack frame is known to work, it's allowed by the standard. At any rate, using objects captured by-reference is not done in terms of name lookup, because you can eg. return the lambda, and the names won't necessarily be in scope when the lambda is invoked. The capturing will obviously use name lookup, but the use of the objects in the lambda body will not.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 6, 7:55 pm, Ville Voutilainen <ville.voutilai...@gmail.com> wrote:
> The copy assignment is deleted so you can't assign lambdas. You can > copy construct them, though. > Unfortunately I don't recall hearing any explanation as to why they > can't be assigned.
Sorry to reply to myself, but Daveed Vandevoorde helpfully pointed out that if a closure class contains reference members, the assignment becomes difficult to do correctly in all cases. That's the reason why lambdas can be copy-constructed but not copy-assigned.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On 2009-11-05, Hyman Rosen <hyro...@mail.com> wrote:
> Kaz Kylheku wrote:
> These C++ closures are a joke anyway; they are not first class.
> That's false. They can be assigned, stored, composed, and called > repeatedly.
Lambda functions that can escape from the environments in which they are created are commonly known as first class functions.
Of course, you are entitled to demand that people accept some other definition ``first class'' when discussing functions with you.
Speaking of composed, to what extent is that true?
Composition of higher order functions means that combinator function accepts some functional arguments, and then returns a new function, which can make use of those functions that were passed in. That is to say, the composed function is a closure is formed inside the combinator which escapes from it. It refers to its constituent functions by means of captured lexical bindings.
> Nevertheless, lambdas will be incredibly useful.
``Incredibly''?
Even in plain C, we can get all the semantic functionality of downward-funarg-only non-lambdas with a simple structure and a function callback pointer. The concept is relatively empty, semantically.
You should be careful throwing adjectives around, because you can embarrass yourself by what you consider incredible.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu<std-c%2B...@netlab.cs.rpi.edu> ] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Ville Voutilainen wrote: > On Nov 3, 8:08 pm, Scott Meyers <use...@aristeia.com> wrote: >> void example1() >> { >> const int x = 5; >> auto f1 = [=]{ ++x; }; // error, f1's copy of x is const >> auto f2 = [=]() mutable { ++x; }; // still an error, x is const, dammit >> }
> That is correct AFAIK. It would be beneficial to see whether that is a > problem
It's a problem in terms of the mental model of the author of the lambda. This would be the only place in C++ where making a copy of a const object yields a const copy without explicitly saying that a const copy is desired. Because non-mutable lambdas yield const operator()s in the resulting closure, capture-by-copy into non-mutable lambdas already has the effect of making const copies, so if the author of the lambda goes to the trouble to turn off this constness by declaring the lambda mutable, it seems to me that the captured-by-copy data members should no longer be const.
What is the motivation for capturing consts as consts when they are captured by copy?
Scott
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
> Lambda functions that can escape from the environments in which > they are created are commonly known as first class functions.
Wikipedia says <http://en.wikipedia.org/wiki/First_class_function>: the language supports constructing new functions during the execution of a program, storing them in data structures, passing them as arguments to other functions, and returning them as the values of other functions. This definition does not require them to be able to access local variables visible in the scope in which they're defined, nor to outlive variables which they can reference.
> Speaking of composed, to what extent is that true?
I'm thinking something like this, although I'm not all that familiar with the proposed syntax. int x = 0; auto f = [&]() { ++x; } auto iterate_f = [=](int n) { return [=]() { while (n-- > 0) f(); } } iterate_f(10)(); I'm not sure if iterate_f can be replaced by a template.
> ``Incredibly''?
Yes, to pass as function objects to templates. It makes the standard library much easier to use.
> Even in plain C, we can get all the semantic functionality of > downward-funarg-only non-lambdas with a simple structure and a function > callback pointer. The concept is relatively empty, semantically.
But not syntactically, which is what matters.
> You should be careful throwing adjectives around, because you > can embarrass yourself by what you consider incredible.
I'm not embarrassed by this particular use of incredible.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Scott Meyers <use...@aristeia.com> writes: > Ville Voutilainen wrote: >> On Nov 3, 8:08 pm, Scott Meyers <use...@aristeia.com> wrote: >>> void example1() >>> { >>> const int x = 5; >>> auto f1 = [=]{ ++x; }; // error, f1's copy of x is const >>> auto f2 = [=]() mutable { ++x; }; // still an error, x is const, dammit >>> }
>> That is correct AFAIK. It would be beneficial to see whether that is a >> problem
> It's a problem in terms of the mental model of the author of the lambda. This > would be the only place in C++ where making a copy of a const object yields a > const copy without explicitly saying that a const copy is desired.
It's also the only place in the language that automatically clones an object.
If you code a clone yourself then you get similar behaviour:
template<typename T> T clone(T&& x) { return T(x);
}
int main() { std::string const a("hello"); clone(a)+=" world"; // error, clone is const std::string b("hello"); clone(b)+=" world"; // OK, clone is non-const clone(std::string("hello"))+=" world"; // OK, clone is non-const typedef const std::string const_string; clone(const_string("hello"))+=" world"; // error, clone is const
} > Because > non-mutable lambdas yield const operator()s in the resulting closure, > capture-by-copy into non-mutable lambdas already has the effect of making const > copies, so if the author of the lambda goes to the trouble to turn off this > constness by declaring the lambda mutable, it seems to me that the > captured-by-copy data members should no longer be const.
Interesting observation.
> What is the motivation for capturing consts as consts when they are captured by > copy?
In a mutable lambda, the type of the cloned object is the same as the source.
[ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 9, 10:59 pm, Scott Meyers <use...@aristeia.com> wrote:
> What is the motivation for capturing consts as consts when they are captured by > copy?
I think decltype of the captured variable in the lambda body would be very different than decltype of it outside the lambda body. If I remember correctly, decltype was one of the reasons why the constness is retained when capturing. Whether that's a convincing argument is another matter. I need to do some digging in order to find out if there were more reasons for this particular decision.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 10, 10:20 pm, Ville Voutilainen <ville.voutilai...@gmail.com> wrote:
> On Nov 9, 10:59 pm, Scott Meyers <use...@aristeia.com> wrote: > > What is the motivation for capturing consts as consts when they are captured by > > copy? > I think decltype of the captured variable in the lambda body would be > very > different than decltype of it outside the lambda body. If I remember > correctly, > decltype was one of the reasons why the constness is retained when > capturing. > Whether that's a convincing argument is another matter. I need to do > some digging > in order to find out if there were more reasons for this particular > decision.
Once again, with the generous help of Daveed Vandevoorde, I found some additional explanation:
begin quoth Daveed One argument is that one would like to translate:
for (int k = 0; k < n; ++k) { /body/ }
into
parfor(0, n, [...](int k) { /body/ });
with /body/ essentially unchanged. (At least, that's my understanding of that aspect of the argument.) end quoth Daveed
For that kind of cases, it's perhaps easier to grok lambdas if they work as a normal loop would. For the loop, any locals are in scope, and if they are const, you can't modify them. For the lambda, the same locals may be implicitly captured, and you still can't modify the captured copies if the originals were const. Overload resolution and decltypes are also the same both in the loop case and in the lambda case.
That sounds like a very good and reasonable explanation to me.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Anthony Williams wrote: > It's also the only place in the language that automatically clones an object.
I'm not sure what you mean here, but I think it's notable that the capture mode is "copy," not "clone." The closure contains a _copy_ of what is captured, not a "clone" (whatever that means).
> If you code a clone yourself then you get similar behaviour:
> int main() > { > std::string const a("hello"); > clone(a)+=" world"; // error, clone is const
No, the error is that you can't bind an lvalue (a) to an rvalue reference (T&&).
Really, I don't understand what you mean by "clone," and even if I did, I would not understand why you seem to believe that the semantics of "copy" should be different for lambdas than for every other part of the language.
Scott
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Ville Voutilainen wrote: > begin quoth Daveed > One argument is that one would like to translate:
> for (int k = 0; k < n; ++k) { > /body/ > }
> into
> parfor(0, n, [...](int k) { > /body/ > });
> with /body/ essentially unchanged. (At least, that's my understanding > of that aspect of the argument.) > end quoth Daveed
> For that kind of cases, it's perhaps easier to grok lambdas if they > work as a normal loop would.
I'm missing something, because (1) there are no lambdas in the above and (2) there is no parfor in or proposed for C++0x. (Or is "[...]" supposed to be a lambda? If so, note that the only case we are talking about is when "[...]" is actually "[=]" _and_ the lambda is mutable; see below.)
> For the loop, any locals are in scope, and if they are const, you > can't modify them. For the lambda, > the same locals may be implicitly captured, and you still can't modify > the captured copies if the > originals were const.
Which, thanks to the implicit constness of operator() in the closure, they'd still be, even if copied in the usual fashion. So the question is why locals captured by copy can't be modified in a mutable lambda.
Scott
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
> No, the error is that you can't bind an lvalue (a) to an rvalue reference (T&&).
Note that clone() uses the perfect forwarding signature where we can bind anything, because the argument lvalueness will determine the actually deduced type.
Greetings from Bremen,
Daniel Krügler
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Scott Meyers <use...@aristeia.com> writes: > Anthony Williams wrote: >> It's also the only place in the language that automatically clones an object.
> I'm not sure what you mean here, but I think it's notable that the capture mode > is "copy," not "clone." The closure contains a _copy_ of what is captured, not > a "clone" (whatever that means).
Sorry. It is the only place where the compiler automatically generates a whole new object of the same type as another, which it copy-constructs from the original. Note that even the reference-ness is preserved. It is a bit like
decltype(x) y(x);
as opposed to
auto y=x;
>> If you code a clone yourself then you get similar behaviour:
>> int main() >> { >> std::string const a("hello"); >> clone(a)+=" world"; // error, clone is const
> No, the error is that you can't bind an lvalue (a) to an rvalue reference (T&&).
As Daniel already pointed out, this is the perfect forwarding scenario, so lvalues bind too. Actually, the example is wrong since it will just return a reference to the original: I really needed to remove the reference type from the return type.
The decltype example above is better.
> Really, I don't understand what you mean by "clone," and even if I did, I would > not understand why you seem to believe that the semantics of "copy" should be > different for lambdas than for every other part of the language.
The semantics of the copy are the same as everywhere else. It is just that the type of the copied object is declared to be *identical* to the type of the original, whereas in most cases you explicitly specify the type of the new object.
[ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Note that clone() uses the perfect forwarding signature where we can bind anything, because the argument lvalueness will determine the actually deduced type.
Yes, I realized after I submitted my post that once again I'd been tripped up by the fact that a T&& parameter in a function template need not generate a function taking a T&& parameter. Silly me. But I still don't understand what cloning has to do with capture-by-copy.
Scott
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 11, 8:49 am, Scott Meyers <use...@aristeia.com> wrote:
> I'm missing something, because (1) there are no lambdas in the above
The [...] is supposed to be a lambda.
> and (2) there is no parfor in or proposed for C++0x.
It is expected that users will try and write such algorithms which take their body as a lambda. The algorithm can run multiple bodies in parallel. There are many other such ideas floating around, and C++0x lambdas intend to support such ideas.
> (Or is "[...]" supposed to be a > lambda? If so, note that the only case we are talking about is when "[...]" is > actually "[=]" _and_ the lambda is mutable; see below.)
Well, you could also write [foo, &bar, &baz] and you would have the same issue with the copy of foo.
> > can't modify them. For the lambda, > > the same locals may be implicitly captured, and you still can't modify > > the captured copies if the > > originals were const. > Which, thanks to the implicit constness of operator() in the closure, they'd > still be, even if copied in the usual fashion. So the question is why locals > captured by copy can't be modified in a mutable lambda.
Mutable lambdas retain the constness of the originals. Immutable lambdas add constness. If the copies drop constness, straightforward transformation from a loop to a copying lambda will result in different decltypes and different overload resolution. That's the reason why the constness is retained. The assumption is that changing decltypes and overload resolution would be a bigger surprise than the surprise of not being able to modify copies of const values. I am unconvinced that that assumption would be incorrect, and I'm personally not willing to propose changes to the behaviour of lambdas in this area.
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
On Nov 9, 7:27 pm, Kaz Kylheku <kkylh...@gmail.com> wrote:
> On 2009-11-05, Hyman Rosen <hyro...@mail.com> wrote:
> > Kaz Kylheku wrote:
> > These C++ closures are a joke anyway; they are not first class.
> > That's false. They can be assigned, stored, composed, and called > > repeatedly.
> Lambda functions that can escape from the environments in which they are > created are commonly known as first class functions.
C++ lambdas are not restricted to downward funargs. The following is a perfectly valid example of a lambda that captures (a copy of) its environment and outlives the scope where it is created.
std::function<int()> hof() { int x = 5; return [=]{ ++x; return x; };
}
...
auto f = hof(); cout << f(); // prints 6 cout << f(); // prints 7
Of course, if you capture by reference, you may get the usual problems of references outliving the referenced value. Just don't do it.
-- Giovanni P. Deretta
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
> Sorry. It is the only place where the compiler automatically generates a > whole new object of the same type as another
...
> The semantics of the copy are the same as everywhere else. It is just > that the type of the copied object is declared to be *identical* to the > type of the original, whereas in most cases you explicitly specify the > type of the new object.
It occurs to me that this is fundamentally my point: the behavior of creation of a member in a closure is not like auto and not like template argument deduction. It's a different thing, but *why* is it a different thing? I realize that C++0x is so simple and regular that there is a need to introduce gratuitous inconsistency from time to time just to keep programmers on their toes, but consider:
const int x = 0;
auto y = x; // y is not const
template<typename T> void f(T y); // y is not const, even if x is passed as an argument
[=]() mutable { ++x; } // the lambda's x, which is a copy // of the local x, is const
Is this really a place where we need to make things more semantically irregular?
Scott
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
> C++ lambdas are not restricted to downward funargs. The following is a > perfectly valid example of a lambda that captures (a copy of) its > environment and outlives the scope where it is created.
> std::function<int()> hof() { > int x = 5; > return [=]{ ++x; return x; }; > }
That should be return [=]()mutable->int{ ++x; return x; }; or return [=]()mutable{ return ++x; };
Cheers, SG
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Scott Meyers <use...@aristeia.com> writes: > Anthony Williams wrote:
>> Sorry. It is the only place where the compiler automatically generates a >> whole new object of the same type as another
> ...
>> The semantics of the copy are the same as everywhere else. It is just >> that the type of the copied object is declared to be *identical* to the >> type of the original, whereas in most cases you explicitly specify the >> type of the new object.
> It occurs to me that this is fundamentally my point: the behavior of > creation of a member in a closure is not like auto and not like > template argument deduction. It's a different thing, but *why* is it > a different thing? I realize that C++0x is so simple and regular that > there is a need to introduce gratuitous inconsistency from time to > time just to keep programmers on their toes, but consider:
> const int x = 0;
> auto y = x; // y is not const
> template<typename T> > void f(T y); // y is not const, even if x is passed as an argument
> [=]() mutable { ++x; } // the lambda's x, which is a copy > // of the local x, is const
> Is this really a place where we need to make things more semantically > irregular?
It's hard to try and be regular, because it depends on what you're comparing to. Consider:
void f() { const int x=42;
++x; // compile error
[=]() mutable { ++x; } // also compile error [&]() mutable { ++x; } // also compile error
int y=42;
++y; // OK
[=]() mutable { ++y; } // OK [&]() mutable { ++y; } // OK
}
Lambdas attempt to be consistent with each other, and with the body of the enclosing function, so that you can freely move between capture-by-copy and capture-by-reference for a lambda, and between some code being part of the normal function body or part of a lambda.
I think it's unfortunate that you need to declare your lambda mutable for complete consistency in this regard, but I also agree with the idea that lambdas should be const by default as it avoids some classes of errors.
[ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
> [=]() mutable { ++y; } // OK > [&]() mutable { ++y; } // OK
Yee haw, they both compile, but the first modifies a copy of y while the second modifies y itself. In order to get correct behavior, the author of the lambda has to understand the transformation from lambda to closure and the implications of copy versus reference capture. My sense is that the "capture by copy, er, we mean this new thing sometimes called clone (which itself is a new meaning of the term 'clone')" rules are designed to help people avoid thinking about the lambda-to-closure transformation. That is, in my view, seriously misguided.
> Lambdas attempt to be consistent with each other, and with the body of > the enclosing function, so that you can freely move between > capture-by-copy and capture-by-reference for a lambda
Which, as shown above, modifies the semantics of the lambda.
> I think it's unfortunate that you need to declare your lambda mutable > for complete consistency in this regard, but I also agree with the idea > that lambdas should be const by default as it avoids some classes of > errors.
Everything avoids some classes of errors, and preventing errors is important. But behavioral inconsistency itself leads to opportunities for errors. Based on the draft standard, my sense is that the committee assigns virtually no value to consistency. How else to explain that omitting parameter lists on non-mutable lambdas is okay, but omitting them on mutable lambdas is not? That functions using trailing return types must be preceded with auto, but lambdas must not? That implicit narrowing conversions are allowed everywhere in the language except when brace initializer lists are used? That unique_ptr is specialized for arrays but shared_ptr is not? That shared_ptr offers special cast forms (e.g., dynamic_pointer_cast), but unique_ptr does not? That the standard documents container "requirements", but *none* of the containers it adds (compared to C++98) satisfy the requirements? The list is close to endless. It's actually a remarkable feat that so many things are so inconsistent. The draft standard doesn't look so much like something developed by committee as something developed by several independent committees.
Scott
-- [ comp.std.c++ is moderated. To submit articles, try just posting with ] [ your news-reader. If that fails, use mailto:std-...@netlab.cs.rpi.edu] [ --- Please see the FAQ before posting. --- ] [ FAQ: http://www.comeaucomputing.com/csc/faq.html ]