Topic: Lambda captures in N2960


Author: Hyman Rosen <hyrosen@mail.com>
Date: Wed, 9 Dec 2009 17:13:48 CST
Raw View
On 12/9/2009 3:05 AM, Kaz Kylheku wrote:
> like Frankenstein's monstrous imitation of the human being

You're not going to get local variables that outlast the
exit of their scope. By-copy lambdas are the best you can
hope for in C++ for that circumstance. Deal with it.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Kaz Kylheku <kkylheku@gmail.com>
Date: Wed, 9 Dec 2009 23:28:20 CST
Raw View
On 2009-12-09, Hyman Rosen <hyrosen@mail.com> wrote:
> On 12/9/2009 3:05 AM, Kaz Kylheku wrote:
>> like Frankenstein's monstrous imitation of the human being
>
> You're not going to get local variables that outlast the
> exit of their scope. By-copy lambdas are the best you can
> hope for in C++ for that circumstance.

Are you saying that by-copy lambdas /can/ outlast the exit of their scope?

That's not the issue at all.

I do think that this implicit by-copy business is simply a misfeature, along
with the whole [&... =] syntax.

Lambdas should capture the actual objects that are there in scope and not make
hidden copies of them.

The designers should come up with a nice syntax for a function literal and
that's that.

   int value;
   auto f = void lambda (int x) { ... }

Here, if you want to capture value, it should be somehow explicit: the copy is
not the original object, and should be explicitly defined, perhaps with a
different, non-shadowing name:

How about this:

   int value;
   auto f = void lambda (int x; int copy = val) { ... }

In the parameter declaration list, there can be a semicolon.  The remaining
declarations after the semicolon are then not parameters, but ordinary
variables local to the lambda. The difference is that if they have
initializers, these initializers are evaluated at lambda-construction time.
Whereas the initializers of variables inside the body are processed at
the time the function is called. Clear.

The member initializer syntax, borrowed from constructors, could also be used,
which is fitting since a lambda is a constructor:

   int val1, val2;
   // ...
   auto f = void lambda (int x; int val1_copy, int val2_copy)
            : val1_copy(val), val2_copy(val2)
            { ... }

I hope someone recognizes the inherent problems in this nasty business of
introducing names into the scope which have no visible definition anywhere, and
whose types are computed according to hidden rules.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Edward Rosten <edward.rosten@gmail.com>
Date: Fri, 11 Dec 2009 12:45:12 CST
Raw View
On Dec 10, 5:28 am, Kaz Kylheku <kkylh...@gmail.com> wrote:

> > You're not going to get local variables that outlast the
> > exit of their scope. By-copy lambdas are the best you can
> > hope for in C++ for that circumstance.
>
> Are you saying that by-copy lambdas /can/ outlast the exit of their scope?

They can be returned. So, the variables it captures may no longer be
in scope.

> I do think that this implicit by-copy business is simply a misfeature, along
> with the whole [&... =] syntax.

Why is it a misfeature any more that pass by value is a misfeature?
This way round it is more consistent with using functions.

> Lambdas should capture the actual objects that are there in scope and not make
> hidden copies of them.

So, you want capturing by reference to be the default?

The copies aren't hidden any more than passing something to a function
causes hidden copies.

> The designers should come up with a nice syntax for a function literal and
> that's that.
>
>    int value;
>    auto f = void lambda (int x) { ... }
>
> Here, if you want to capture value, it should be somehow explicit: the copy is

Why should it be that way round? C++ generally does things by value,
not by reference. You only get by reference behaviour by asking for
it.

-Ed



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Fri, 11 Dec 2009 16:36:43 CST
Raw View
On 12/11/2009 1:45 PM, Edward Rosten wrote:
> On Dec 10, 5:28 am, Kaz Kylheku<kkylh...@gmail.com>  wrote:
>> Here, if you want to capture value, it should be somehow explicit: the copy is
>
> Why should it be that way round? C++ generally does things by value,
> not by reference. You only get by reference behaviour by asking for it.

For the same reason already mentioned; the code inside the
lambda should work as much as possible like the code outside
the lambda. That means that if code mentions a variable, it
gets the variable it mentions. By-copy is a workaround for
lambdas outlasting their scope. As such, it should not be the
default.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Kaz Kylheku <kkylheku@gmail.com>
Date: Sat, 12 Dec 2009 20:22:02 CST
Raw View
On 2009-12-11, Hyman Rosen <hyrosen@mail.com> wrote:
> On 12/11/2009 1:45 PM, Edward Rosten wrote:
>> On Dec 10, 5:28 am, Kaz Kylheku<kkylh...@gmail.com>  wrote:
>>> Here, if you want to capture value, it should be somehow explicit: the copy is
>>
>> Why should it be that way round? C++ generally does things by value,
>> not by reference. You only get by reference behaviour by asking for it.
>
> For the same reason already mentioned; the code inside the
> lambda should work as much as possible like the code outside
> the lambda. That means that if code mentions a variable, it
> gets the variable it mentions. By-copy is a workaround for
> lambdas outlasting their scope. As such, it should not be the
> default.

You express this very clearly.

I like the compromise that those variables which are const can be
captured in a way that outlasts the scope.

This should be the only mechanism for obtaining captures that outlast.
I.e. you trade variability for duration.

Then we don't need any braindamaged [&=,...] syntax.

Just a nice keyword  (I used lambda but it can be something else).

Really, if you are giong to have lambdas, it makes sense to
have named local functions.

Some languages have only named local functions: if you want a lambda,
you write a named function, and then use its name to obtain a reference
to the functino. Python is this way (though it has a very limited lambda
for single expressions). Wirth's Pascal also: local functions are named,
but you can indirect upon them. GNU C, ditto.

Why is C++ introducing anonymous function literals, but with no
ability to make named ones?

What's wrong with:

 int (*fun)();

 {
    int x = 42;      // this is captured, turns to dust
    const int y = x; // this endures

    // named local function
    int local()
    {
       return x + y;
    }

    int (*plambda)() = local; // closure

    plambda();  /* returns 42 + 42 */

    fun = plambda; /* escape! */
  }

  fun(); // undefined behavior: in x + y, y is valid,
         // x is indeterminate


Now having this, we can contemplate how we might be able to write a
function literal: a syntax which denotes an unnamed function, outside of
a declaration. For that a keyword like ``lambda'', ``function''
``fun'', etc, is helfpul.

This could syntactically work as a storage class specifier, so
that the return type could follow:

I.e., in the following. lambda does not stand in the place of
a function name; if the function had a name, it would go
between the int specifier, and the ():

 int (*plambda)() = lambda int () { return x + y; }

I.e. the lambda keyword token tells the parser that what follows is a
special function definition syntax in which the declarator does not have
a name in the expected place.

Instead of lambda, the auto keyword could be used once more:

 int (*plambda)() = auto int () { return x + y; }

:)

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Kaz Kylheku <kkylheku@gmail.com>
Date: Sat, 12 Dec 2009 20:21:17 CST
Raw View
On 2009-12-11, Edward Rosten <edward.rosten@gmail.com> wrote:
> On Dec 10, 5:28 am, Kaz Kylheku <kkylh...@gmail.com> wrote:
>
>> > You're not going to get local variables that outlast the
>> > exit of their scope. By-copy lambdas are the best you can
>> > hope for in C++ for that circumstance.
>>
>> Are you saying that by-copy lambdas /can/ outlast the exit of their scope?
>
> They can be returned. So, the variables it captures may no longer be
> in scope.
>
>> I do think that this implicit by-copy business is simply a misfeature, along
>> with the whole [&... =] syntax.
>
> Why is it a misfeature any more that pass by value is a misfeature?
> This way round it is more consistent with using functions.
>
>> Lambdas should capture the actual objects that are there in scope and not make
>> hidden copies of them.
>
> So, you want capturing by reference to be the default?

Of course. Local functions that don't actually have access to the real
variables in the parent function are ridiculous.  (Regardless of the
question of lifetime).

> The copies aren't hidden any more than passing something to a function
> causes hidden copies.

I think I have not made myself clear. Passnig someting toa  function
does not make a hidden copy because a function has formally declared
arguments (at least a non-variadic, regular function).

I'm not concerned about the copying itself being hidden, but rather the
introduction of invisible variables which do not appear in any
declaration written in the program text.

Semantically, this is precisely what is going on.   If some variable
``int x'' in the scope is captured by value in a lambda, in fact, what
is captured is a new object x which lexically shadows that one.  Only,
this object x is not declared.

This is completely wrong-headed, like the Pascal WITH statement.

Now you have the situation that some code accesses an identifier 'x',
which is a lexical variable, the scope is completely void of any
definition of this object. What's worse, there /is/ a declaration
for someting called `x', but it's not /that/ x which is being
referenced. Oops!

C++ constructs must not introduce hidden definitions into the lexical
scope (not ones which are intended to be referenced from the program
text). Any such a proposal must die.

>> The designers should come up with a nice syntax for a function literal and
>> that's that.
>>
>>    int value;
>>    auto f = void lambda (int x) { ... }
>>
>> Here, if you want to capture value, it should be somehow explicit: the copy is
> Why should it be that way round? C++ generally does things by value,
> not by reference. You only get by reference behaviour by asking for
> it.

I would say that C++ does things by value, where ``things'' are
assignment and parameter passing.

Providing transparency access to a lexical scope is not like assignment
or parameter passing.

If you look toward existing languages which have closures, they all work
by providing access to the actual variables, and not some redefined
copies.

In Pascal, local functions give you access to the parent's actual
variabls.

In GNU C, the local function extension, ditto.

Closures in Python, Lisp, Scheme, and probably all functional languages;
ML, Haskell, ocaml, ..

Java local classes are restricted to capturing final variables only.
This can be regarded as real capture which does not make copies,
but is just restricted to finals. The local class can be regarded
to really be making an actual lexical reference to the final
variables, and not some shadowing redeclarations.

A compromise similar to Java local classes would not be bad.  Let's
substitute ``const'' for ``final''. Captured objects which are defined
const can have a lifetime tied to the lambda: references to them do not
become dust when their defining scope terinates. This can work by making
copies, but without manipulating the lexical scope.  Objects that are
not const are actually captured; they become dust when their defining
block terminates.

This is not a bad solution also. If you want to sample the value of a
non-const variable, and captuer it into a lambda in a persistent way,
you can now make your own shadow copy, This is good because it has a
visible definition:


  int variable_x;

  /* ... */

     const int immutable_snapshot_x = variable_x;

     /* now make a closure whose body uses immutable_snapshot_x */

Everything is nice and explicit; every ref has a matching def in
the lexical scope.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Wed, 2 Dec 2009 22:27:34 CST
Raw View
Scott Meyers wrote:
> 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?

Someone already pointed out why this should be - when you
take a block of code and stick it into a lambda, you should
not change its semantics.

     void f(const int &) { } // #1
     void f(      int &) { } // #2
     void g() {
         int const x;
         f(x); // Calls #1
         auto l = [=] mutable { f(x); };
         l();  // Should call #1, not #2.
     }

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Wed, 2 Dec 2009 22:27:51 CST
Raw View
Scott Meyers wrote:
> 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?

Someone already pointed out why this should be - when you
take a block of code and stick it into a lambda, you should
not change its semantics.

      void f(const int &) { } // #1
      void f(      int &) { } // #2
      void g() {
          int const x;
          f(x); // Calls #1
          auto l = [=] mutable { f(x); };
          l();  // Should call #1, not #2.
      }

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Wed, 2 Dec 2009 22:30:40 CST
Raw View
Scott Meyers wrote:
>
> 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?

Someone already pointed out why this should be - when you
take a block of code and stick it into a lambda, you should
not change its semantics.

   void f(const int &) { } // #1
   void f(      int &) { } // #2
   void g() {
       int const x;
       f(x); // Calls #1
       auto l = [=] mutable { f(x); };
       l();  // Should call #1, not #2.
   }

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Kaz Kylheku <kkylheku@gmail.com>
Date: Thu, 3 Dec 2009 01:04:01 CST
Raw View
On 2009-12-03, Hyman Rosen <hyrosen@mail.com> wrote:
> Scott Meyers wrote:
>>
>> 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?
>
> Someone already pointed out why this should be - when you
> take a block of code and stick it into a lambda, you should
> not change its semantics.

Now wait a second.

 (defparameter x 42)

 (+ x x)  ;; yields 84 here

 (lambda (x)
   (+ x x))   ;; here stuck in a lambda, still 84?

 ;; how about this

 (defmacro screw-with-environment (&forms)
   `(let ((x -1)) ,@forms))

 (screw-with-environment (+ x x)) ;; still 84?

If you stick a body of code somewhere where the references which emanate from
that code are interpreted in exactly the same environment as in the original
location, then its intepretation should not change. That much is clear.

If that body is inserted into a different environment (typographically, without
some referentially transparent relocation), then the semantics does
potentially change.

>    void f(const int &) { } // #1
>    void f(      int &) { } // #2
>    void g() {
>        int const x;
>        f(x); // Calls #1
>        auto l = [=] mutable { f(x); };
>        l();  // Should call #1, not #2.
>    }

So is it the case here that the expression f(x); inside the lambda is in the
same environment as the previous f(x)?  (Of course, its environment includes
the binding for the auto l, which is not in the scope of the first f(x), but
never mind; the question revolves around just x).

Note that [] is a binding construct which changes the environment, like the
screw-with-environment Lisp macro above. In the case of =, new local
variables are established which are copies of the ones which have
the same name, or something like that. So there is an x in scope, but it's not
the same x any more. It's a new variable which takes on an initial value from
that x, just like a shadowing situation:

 {
   const int x = 42; // must be initialized, by the way

   {
     int x = x;  // fantasy construct: not allowed by scoping rules
                 // but just imagine.
     ...
   }
 }

So regardless of which f overload is called, it is not getting a reference to
the outer x, but to the redefined inner x. That much is clear.

Now if this mutable means ``when rebinding these outer variables to produce the
lambda copies, generate the types for these new variables by stripping
off the const qualifier from the types of their corresponding outer
variables'', then it is clear that the inner x has the unqualified type
``int''.  Therefore, #2 should be called.

If overload resolution is to pick #1 over #2, it means that the inner x must
have type ``const int x''.  But then if that's the case, how is x mutable?  If
calling #1 does not imply any contradiction, what does this mutable mean?
What is being declared mutable?

In any case, the body of a lambda cannot be subject to a referential
transparency which passes through the [] binding construct, because then that
construct is pointless.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Thu, 3 Dec 2009 12:02:24 CST
Raw View
Kaz Kylheku wrote:

    Now wait a second.
     (defparameter x 42)
     (+ x x)  ;; yields 84 here
     (lambda (x) (+ x x))   ;; here stuck in a lambda, still 84?


Well, no, not when you introduce a new 'x' which
hides the other one.

     ;; how about this
     (defmacro screw-with-environment (&forms)
      `(let ((x -1)) ,@forms))
     (screw-with-environment (+ x x)) ;; still 84?


Sorry, I don't speak Lisp well enough for this one.

              f(x); // Calls #1
              auto l = [=] mutable { f(x); };


    So is it the case here that the expression f(x); inside the

> lambda is in the same environment as the previous f(x)?

Well, that's the question, isn't it? My feeling is that the
answer should be yes, to the extent possible. (I probably
shouldn't be barging in to this discussion, though - I haven't
followed enough of the lambda discussions to understand why
someone would want the opposite.)

> In the case of =, new local variables are established which
> are copies of the ones which have the same name, or something
> like that. So there is an x in scope, but it's not the same x
> any more. It's a new variable which takes on an initial value
> from that x, just like a shadowing situation. So regardless of
> which f overload is called, it is not getting a reference to

    the outer x, but to the redefined inner x. That much is clear.


Yes.

    Now if this mutable means ``when rebinding these outer variables

> to produce the lambda copies, generate the types for these new
> variables by stripping off the const qualifier from the types of
> their corresponding outer variables'', then it is clear that the
> inner x has the unqualified type ``int''.
> Therefore, #2 should be called.

That's not what it means, though.

    If overload resolution is to pick #1 over #2, it means that the

> inner x must have type ``const int x''.  But then if that's the
> case, how is x mutable?  If calling #1 does not imply any
> contradiction, what does this mutable mean?
> What is being declared mutable?

The function call operator of the lambda reference. That is,
if the lambda is not mutable, its function call operator may
not change the values of its copied variables. If it is mutable,
it may change the values of any variables whose type allows that.
But if those variables are const, they may not be changed anyway.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <NeverRead@aristeia.com>
Date: Sat, 5 Dec 2009 22:31:05 CST
Raw View
Hyman Rosen wrote:
> Someone already pointed out why this should be - when you
> take a block of code and stick it into a lambda, you should
> not change its semantics.
>
>     void f(const int &) { } // #1
>     void f(      int &) { } // #2
>     void g() {
>         int const x;
>         f(x); // Calls #1
>         auto l = [=] mutable { f(x); };
>         l();  // Should call #1, not #2.
>     }

And I already replied explaining why that argument is specious:

> If the goal is to have overload resolution on by-copy captures work
> the same way both inside and outside the lambda, bear in mind that
> immutable lambdas, as noted above, add constness to local variables,
> thus possibly changing overload resolution:
>
> #include <iostream>
>
> void f(      int &) { std::cout << "      int&\n"; }
> void f(const int &) { std::cout << "const int&\n"; }
>
> int main()
> {
> int i = 0;
> int const ci = 0;
>
> f(i);    //       int&
> f(ci);   // const int&
>
> auto l1i  = [=] { f(i); };
> auto l1ci = [=] { f(ci); };
> l1i();   // const int& -- NOT THE SAME AS OUTSIDE THE LAMBDA
> l1ci();  // const int&
> }

Scott

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Mon, 7 Dec 2009 11:24:08 CST
Raw View
Scott Meyers wrote:
> Hyman Rosen wrote:
>> when you take a block of code and stick it into a lambda,
  >> you should not change its semantics.
>
> And I already replied explaining why that argument is specious:
>
>> bear in mind that immutable lambdas, as noted above, add constness
  >> to local variables, thus possibly changing overload resolution:

Then I submit that the problem lies with immutable lambdas.
I may have missed earlier arguments, but I don't see the
purpose of having them. The semantics of by-copy lambdas
ought to be that you get a copy of any captured locals with
their declared types, and it has a non-const function call
operator.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Kaz Kylheku <kkylheku@gmail.com>
Date: Wed, 9 Dec 2009 02:05:39 CST
Raw View
On 2009-12-07, Hyman Rosen <hyrosen@mail.com> wrote:
> Scott Meyers wrote:
>> Hyman Rosen wrote:
>>> when you take a block of code and stick it into a lambda,
>  >> you should not change its semantics.
>>
>> And I already replied explaining why that argument is specious:
>>
>>> bear in mind that immutable lambdas, as noted above, add constness
>  >> to local variables, thus possibly changing overload resolution:
>
> Then I submit that the problem lies with immutable lambdas.
> I may have missed earlier arguments, but I don't see the
> purpose of having them.

The purpose is to be consistent with the spirit of C++.  If other languages
have a feature which is like a human being, the C++ ``re animation'' of that
feature must be like Frankenstein's monstrous imitation of the human being.

Or, if you will, a good metaphor in around this time of year is that the C++
implementation of something should not be like Christmas, but more like Jack
Skellington's twisted concept of it in the animated film _The Nightmare
Before Christmas_.

:)

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Dragan Milenkovic <dragan@plusplus.rs>
Date: Sun, 15 Nov 2009 00:50:25 CST
Raw View
Anthony Williams wrote:
>
> 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 completely agree with you regarding capture-by-reference. It is
similar to Java's inner classes.

But from my perspective, using capture-by-value means I want to
introduce a state to the closure. I was "offline" for quite some time,
but I guess that "mutable" is the way to control the mutability
of the captured state. But it fails, for the reasons Scott mentioned.
It only makes the state mutable if the original is mutable. I don't
care for the original, I want a full control of my state.

Anyway, If all a programmer cares is to extend the life of a variable,
capture by value, which is const by default, will get him/her there.

I don't disagree that you should be able to freely move code in
and out of lambdas, but as pointed in another branch of this thread,
const lambdas fail here. IMHO, the logic seems flawed.

My ideal world looks like this:

 void f(Foo &);        #1
 void f(Foo const &);  #2

 void g() {
    Foo foo1;
    const Foo foo2;

    f(foo1);      #1
    f(foo2);      #2

      [&]() { f(foo1); }    #1
      [&]() { f(foo2); }    #2

      [=]() mutable { f(foo1); }  #1
      [=]() mutable { f(foo2); }  #1 (as opposed to the current #2)

      [=]() { f(foo1); }    #2   (notice the "wrong" overload)
      [=]() { f(foo2); }    #2
 }

... ah... sweet symmetry...

IMHO, a logical solution would be to have const, mutable and retain.

Anyway, what happened to move-capture [=&&] ? :-)

--
Dragan

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Tue, 10 Nov 2009 14:20:23 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Tue, 10 Nov 2009 17:00:47 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Tue, 10 Nov 2009 17:00:39 CST
Raw View
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:
>
> template<typename T>
> T clone(T&& x)
> {
>    return T(x);
> }
>
> 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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Wed, 11 Nov 2009 00:49:31 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Wed, 11 Nov 2009 00:50:08 CST
Raw View
On 11 Nov., 00:00, Scott Meyers <use...@aristeia.com> wrote:
> Anthony Williams wrote:
[..]
> > 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
>
> 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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Wed, 11 Nov 2009 12:07:07 CST
Raw View
Scott Meyers <usenet@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:
>>
>> template<typename T>
>> T clone(T&& x)
>> {
>>    return T(x);
>> }
>>
>> 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.

Anthony
--
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library   | http://www.stdthread.co.uk
Just Software Solutions Ltd         | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Wed, 11 Nov 2009 12:06:46 CST
Raw View
Daniel Kr   gler wrote:

    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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Wed, 11 Nov 2009 12:07:37 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: gpderetta <gpderetta@gmail.com>
Date: Wed, 11 Nov 2009 12:08:00 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Wed, 11 Nov 2009 17:19:42 CST
Raw View
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?

Scott

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: SG <s.gesemann@gmail.com>
Date: Wed, 11 Nov 2009 17:21:16 CST
Raw View
On 11 Nov., 19:08, gpderetta wrote:
>
> 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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Thu, 12 Nov 2009 11:25:17 CST
Raw View
Scott Meyers <usenet@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.

Anthony
--
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library   | http://www.stdthread.co.uk
Just Software Solutions Ltd         | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Thu, 12 Nov 2009 16:24:12 CST
Raw View
Anthony Williams wrote:
>      ++y; // OK
>
>       [=]() 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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Thu, 12 Nov 2009 19:45:07 CST
Raw View
Ville Voutilainen wrote:
>
> 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 didn't really understand what you meant by this, but I received
email as follows:

> When you take a block of code and stick it into a lambda, you should
> not change its semantics.
>
>     void f(const int &) { } // #1
>     void f(      int &) { } // #2
>     void g() {
>         int const x;
>         f(x); // Calls #1
>         auto l = [=] mutable { f(x); };
>         l();  // Should call #1, not #2.
>     }

If the goal is to have overload resolution on by-copy captures work
the same way both inside and outside the lambda, bear in mind that
immutable lambdas, as noted above, add constness to local variables,
thus possibly changing overload resolution:

#include <iostream>

void f(      int &) { std::cout << "      int&\n"; }
void f(const int &) { std::cout << "const int&\n"; }

int main()
{
 int i = 0;
 int const ci = 0;

 f(i);    //       int&
 f(ci);   // const int&

 auto l1i  = [=] { f(i); };
 auto l1ci = [=] { f(ci); };
 l1i();   // const int& -- NOT THE SAME AS OUTSIDE THE LAMBDA
 l1ci();  // const int&
}

So explain to me again how the rules for transforming lambda to
closure make sense?  In particular, explain why I get const operator()
functions by default (in contrast to everything else in C++, where you
only get constness when you request it, thus necessitating the need to
give mutable a new meaning for lambdas) and explain why "cloning" the
cv qualifiers for by-value captures is a good thing, given that it
would not be done for auto-generated objects or through template type
deduction for pass-by-value parameters.

Thanks,

Scott

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Fri, 13 Nov 2009 11:31:03 CST
Raw View
Scott Meyers <usenet@aristeia.com> writes:

> Anthony Williams wrote:
>>      ++y; // OK
>>
>>       [=]() 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.

Of course, if you're going to change from copy to reference capture or
vice-versa then you need to know what the semantics are. My point is
that the ONLY change is to whether or not the original y is
modified. Everything else works the same in terms of name lookup,
overload resolution and so forth.

>> 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.

This is partly a consequence of the way it is developed --- people
propose individual changes which are then accepted or not. The committee
works hard to achieve consistency, but doesn't always manage. Sometimes
the inconsistencies are deliberate, and sometimes they are
oversights. Overall, I think the result is OK.

Anthony
--
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library   | http://www.stdthread.co.uk
Just Software Solutions Ltd         | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Fri, 13 Nov 2009 11:57:07 CST
Raw View
On Nov 13, 12:24 am, Scott Meyers <use...@aristeia.com> wrote:
> Based on
> the draft standard, my sense is that the committee assigns virtually no value to
> consistency.

I wouldn't personally make such bold claims about a group of 50
people. The consistency
issues that you point out are interesting, I'll see if anything can be
done about them.

> The draft standard doesn't look so much like something developed by committee as
> something developed by several independent committees.

That is, in certain sense, exactly the case. :)


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Fri, 13 Nov 2009 13:31:35 CST
Raw View
On Nov 13, 3:45 am, Scott Meyers <use...@aristeia.com> wrote:
> So explain to me again how the rules for transforming lambda to
> closure make sense?  In particular, explain why I get const operator()
> functions by default (in contrast to everything else in C++, where you

It was actually suggested in N2651 that non-const would be default and
you would have to declare the lambda const to get constness. N2658
suggested
mutable, based on the feedback from the Core Working Group. The
rationale
given was that having to write const for stateless lambdas would be
"invitation to users to do something they weren't expected to do". I
don't
know all the details of that discussion, because I wasn't present in
Sophia
Antipolis.

> give mutable a new meaning for lambdas) and explain why "cloning" the
> cv qualifiers for by-value captures is a good thing, given that it

I believe it's better than not cloning the cv qualifiers.

> would not be done for auto-generated objects or through template type

It would be done for decltype-generated objects, I think. You can also
say

const auto x = foo();
vs.
auto x = foo();

and while that is not consistent with lambdas, I don't see anything
wrong
with that inconsistency.

> deduction for pass-by-value parameters.

That case is different from the consistency a lambda tries to achieve
with its
surrounding scope, I think. I don't see template type deduction at all
closely
related to lambdas, I don't quite grasp why they'd have to work the
same way
when the different behaviour in each makes sense. It may not make
sense
to everybody. It did seem to make sense to the CWG, and to the full
committee
too, since I haven't heard challenges to the const-default of lambdas.

I still don't see the lambda rules as grossly wrong.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Fri, 13 Nov 2009 14:13:28 CST
Raw View
Ville Voutilainen wrote:
>
> On Nov 13, 12:24 am, Scott Meyers <use...@aristeia.com> wrote:
>>
>> Based on
>> the draft standard, my sense is that the committee assigns virtually no value to
>> consistency.
>
> I wouldn't personally make such bold claims about a group of 50
> people.

I'm not making any claims about people.  I'm making a claim about the
document they have developed (*).  They people themselves may value
consistency greatly, but if so, the document does not reflect that,
IMO.

Scott

(*) I realize that the document is a draft, and the final standard
will be different.  But that doesn't change my evalation of the
current document's content.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Tue, 3 Nov 2009 12:08:26 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Kaz Kylheku <kkylheku@gmail.com>
Date: Wed, 4 Nov 2009 21:39:51 CST
Raw View
On 2009-11-03, Scott Meyers <usenet@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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Thu, 5 Nov 2009 16:28:04 CST
Raw View
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.

    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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: CornedBee <wasti.redl@gmx.net>
Date: Thu, 5 Nov 2009 16:27:36 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Thu, 5 Nov 2009 17:27:23 CST
Raw View
> 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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Fri, 6 Nov 2009 11:55:21 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Fri, 6 Nov 2009 11:57:01 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Ville Voutilainen <ville.voutilainen@gmail.com>
Date: Fri, 6 Nov 2009 14:56:17 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Kaz Kylheku <kkylheku@gmail.com>
Date: Mon, 9 Nov 2009 12:27:04 CST
Raw View
On 2009-11-05, Hyman Rosen <hyrosen@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-c++@netlab.cs.rpi.edu<std-c%2B%2B@netlab.cs.rpi.edu>
]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Scott Meyers <usenet@aristeia.com>
Date: Mon, 9 Nov 2009 14:59:26 CST
Raw View
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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Mon, 9 Nov 2009 15:36:49 CST
Raw View
Kaz Kylheku wrote:
>
> 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-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Tue, 10 Nov 2009 14:19:01 CST
Raw View
Scott Meyers <usenet@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.

Anthony
--
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library   | http://www.stdthread.co.uk
Just Software Solutions Ltd         | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976

[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]