Topic: Closures (C++0x)
Author: Steve Clamage <clamage@eng.sun.com>
Date: Wed, 30 May 2001 21:44:44 GMT Raw View
On Tue, 29 May 2001, Dave Harris wrote:
>
> I am assuming the language will be changed to allow temporaries to be
> bound to non-const arguments.
Why would you assume that? You cannot bind a temp to a non-const
reference for sound reasons: in general it results in code that compiles
silently but does not do what you want or expect.
---
Steve Clamage, stephen.clamage@sun.com
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Anthony Williams" <anthwil@nortelnetworks.com>
Date: Thu, 31 May 2001 15:54:49 GMT Raw View
"Steve Clamage" <clamage@eng.sun.com> wrote in message
news:Pine.SOL.3.96.1010530142550.4985D-100000@taumet...
> On Tue, 29 May 2001, Dave Harris wrote:
> >
> > I am assuming the language will be changed to allow temporaries to be
> > bound to non-const arguments.
>
> Why would you assume that? You cannot bind a temp to a non-const
> reference for sound reasons: in general it results in code that compiles
> silently but does not do what you want or expect.
>
Even with the lifetime-extending properties associated with binding
temporaries to const references?
If I really want to, I can get a non-const reference to a temporary by
providing a member function to do just that:
class X
{
public:
X& getRef() const
{
return *const_cast<X*>(this);
}
};
const X& tempRef=X();
X& ref=tempRef.getRef();
I know that the original temp is non-const because I created it, so the
const_cast<> is safe. The object is destroyed when tempRef goes out of
scope.
How is that different from
X& ref2=X();
apart from the second being currently disallowed?
Anthony
--
Anthony Williams
Software Engineer, Nortel Networks Optoelectronics
The opinions expressed in this message are not necessarily those of my
employer
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Mirek Fidler" <cxl@volny.cz>
Date: Thu, 31 May 2001 19:35:23 GMT Raw View
> > I am assuming the language will be changed to allow temporaries to be
> > bound to non-const arguments.
>
> Why would you assume that? You cannot bind a temp to a non-const
> reference for sound reasons: in general it results in code that compiles
> silently but does not do what you want or expect.
Well, but these cases are very rare and avoidable and same is ussually
true for const reference arguments.
On the other side, disallowing this often forces you to perform
non-const operations to const reference parameters (using const_cast or
mutable). See standard library auto_ptr as a perfect example. Personally, I
see it as the biggest problem in current C++.
Mirek
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: brangdon@cix.co.uk (Dave Harris)
Date: Thu, 31 May 2001 21:44:15 GMT Raw View
clamage@eng.sun.com (Steve Clamage) wrote (abridged):
> > I am assuming the language will be changed to allow temporaries to
> > be bound to non-const arguments.
>
> Why would you assume that?
It simplified some aspects of my exposition. In general, code which does
powerful stuff with expressions is crippled if temporaries are treated as
second class citizens.
> You cannot bind a temp to a non-const reference for sound reasons:
> in general it results in code that compiles silently but does not
> do what you want or expect.
As I said in the part you snipped, this has been discussed in a different
comp.std.c++ thread. I'll summarise here anyway because it is quite hard
to find in all the noise, and because I think it's important.
The main concern was temporaries which are created by implicit
conversions. The real problem here is implicit conversions, not
references to temporaries. Christopher Eltschka proposed disallowing
implicit conversions in this situation. Thus:
void proc( long &arg ) { ++arg; cout << arg; }
void demo() {
int x = 0;
proc( x ); // Compiler error: int->long conversion.
proc( 0 ); // Compile error: int->long conversion.
proc( long(x) ); // OK! Conversion is explicit.
proc( 0L ); // OK! No conversion.
}
Several people seemed to think this was a good compromise. I think it's
brilliant. I didn't see anyone speak against it.
Is your objection covered by the above? Most people seem concerned about
the first call, where we might expect x to get the post-increment value
but it won't. The current proposal disallows such cases.
In the last case, the result of the increment is thrown away when proc()
ends, but I don't see any reason to suppose this is not wanted or
expected. It is not much different to ignoring a function's return value.
There is another set of problems to do with holding onto the reference
after the temporary has gone out of scope and been destroyed. However,
these issues arise whether or not the reference is const, or for that
matter whether or not the argument is a temporary. It is a ticklish area
and I think an arbitrary difference between const and non-const makes it
worse, not better.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
brangdon@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: James Dennett <jdennett@acm.org>
Date: Thu, 31 May 2001 22:23:32 GMT Raw View
Anthony Williams wrote:
>
> "Steve Clamage" <clamage@eng.sun.com> wrote in message
> news:Pine.SOL.3.96.1010530142550.4985D-100000@taumet...
> > On Tue, 29 May 2001, Dave Harris wrote:
> > >
> > > I am assuming the language will be changed to allow temporaries to be
> > > bound to non-const arguments.
> >
> > Why would you assume that? You cannot bind a temp to a non-const
> > reference for sound reasons: in general it results in code that compiles
> > silently but does not do what you want or expect.
> >
> Even with the lifetime-extending properties associated with binding
> temporaries to const references?
>
> If I really want to, I can get a non-const reference to a temporary by
> providing a member function to do just that:
>
> class X
> {
> public:
> X& getRef() const
> {
> return *const_cast<X*>(this);
> }
> };
>
> const X& tempRef=X();
> X& ref=tempRef.getRef();
You don't need the const_cast -- you can get away with
struct X
{
X& getRef() { return *this; } // non-const method
};
X& ref = X().getRef(); // call non-const method on temporary
C++ is strangely asymmetrical in that a non-const reference
can only be bound to a temporary as *this.
-- James Dennett
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: remove.haberg@matematik.su.se (Hans Aberg)
Date: Fri, 1 Jun 2001 16:34:32 GMT Raw View
In article <memo.20010531221327.46715E@brangdon.madasafish.com>,
brangdon@cix.co.uk wrote:
>The main concern was temporaries which are created by implicit
>conversions. The real problem here is implicit conversions, not
>references to temporaries. Christopher Eltschka proposed disallowing
>implicit conversions in this situation. Thus:
>
> void proc( long &arg ) { ++arg; cout << arg; }
>
> void demo() {
> int x = 0;
> proc( x ); // Compiler error: int->long conversion.
> proc( 0 ); // Compile error: int->long conversion.
> proc( long(x) ); // OK! Conversion is explicit.
> proc( 0L ); // OK! No conversion.
> }
>
>Several people seemed to think this was a good compromise. I think it's
>brilliant. I didn't see anyone speak against it.
I think my compiler can handle this in the case of classes:
class A;
class B {
public:
B(const A&);
};
void f(B&);
void g() {
A x;
f(x); // Compiler error: implicit A& -> B& conversion.
f(B(x)); // OK! Conversion is explicit.
}
What I would want to see is the addition that when the ref & constructor
is defined, then an implicit conversion is also allowed:
class B {
public:
B(A&);
};
void g() {
A x;
f(x); // OK implicit A& -> B& conversion, but B::B(A&) defined.
}
This is needed in when programming with dynamic objects:
The best solution with current C++ seems to be to define all dynamic
objects "mutable", and then make use of the B::B(const B&) and B::B(const
A&) constructors in lieu of the corresponding ones with "const" dropped,
which is somewhat corny.
In addition, if there is a conservative GC in place, it can keep track of
bindings, as it traces the live objects from the root set.
It might be the case that if one writes a conservative GC for the class A
above, then one might need to write an A::A(A&) constructor which is
called to tell the runtime system to keep the object alive. -- I am not
sure exactly what is needed here.
Hans Aberg * Anti-spam: remove "remove." from email address.
* Email: Hans Aberg <remove.haberg@member.ams.org>
* Home Page: <http://www.matematik.su.se/~haberg/>
* AMS member listing: <http://www.ams.org/cml/>
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Anthony Williams" <anthwil@nortelnetworks.com>
Date: Fri, 1 Jun 2001 19:13:09 GMT Raw View
"James Dennett" <jdennett@acm.org> wrote in message
news:3B16C2B8.E70668B2@acm.org...
> Anthony Williams wrote:
> >
> > "Steve Clamage" <clamage@eng.sun.com> wrote in message
> > news:Pine.SOL.3.96.1010530142550.4985D-100000@taumet...
> > > On Tue, 29 May 2001, Dave Harris wrote:
> > > >
> > > > I am assuming the language will be changed to allow temporaries to
be
> > > > bound to non-const arguments.
> > >
> > > Why would you assume that? You cannot bind a temp to a non-const
> > > reference for sound reasons: in general it results in code that
compiles
> > > silently but does not do what you want or expect.
> > >
> > Even with the lifetime-extending properties associated with binding
> > temporaries to const references?
> >
> > If I really want to, I can get a non-const reference to a temporary by
> > providing a member function to do just that:
> >
> > class X
> > {
> > public:
> > X& getRef() const
> > {
> > return *const_cast<X*>(this);
> > }
> > };
> >
> > const X& tempRef=X();
> > X& ref=tempRef.getRef();
>
> You don't need the const_cast -- you can get away with
>
> struct X
> {
> X& getRef() { return *this; } // non-const method
> };
>
> X& ref = X().getRef(); // call non-const method on temporary
>
> C++ is strangely asymmetrical in that a non-const reference
> can only be bound to a temporary as *this.
Except that this doesn't extend the lifetime of the temporary, so the
reference is immediately invalid.
Anthony
--
Anthony Williams
Software Engineer, Nortel Networks Optoelectronics
The opinions expressed in this message are not necessarily those of my
employer
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: James Dennett <jdennett@acm.org>
Date: Sat, 2 Jun 2001 10:15:51 GMT Raw View
Anthony Williams wrote:
>
> "James Dennett" <jdennett@acm.org> wrote in message
> news:3B16C2B8.E70668B2@acm.org...
> > Anthony Williams wrote:
> > >
> > > "Steve Clamage" <clamage@eng.sun.com> wrote in message
> > > news:Pine.SOL.3.96.1010530142550.4985D-100000@taumet...
> > > > On Tue, 29 May 2001, Dave Harris wrote:
> > > > >
> > > > > I am assuming the language will be changed to allow temporaries to
> be
> > > > > bound to non-const arguments.
> > > >
> > > > Why would you assume that? You cannot bind a temp to a non-const
> > > > reference for sound reasons: in general it results in code that
> compiles
> > > > silently but does not do what you want or expect.
> > > >
> > > Even with the lifetime-extending properties associated with binding
> > > temporaries to const references?
> > >
> > > If I really want to, I can get a non-const reference to a temporary by
> > > providing a member function to do just that:
> > >
> > > class X
> > > {
> > > public:
> > > X& getRef() const
> > > {
> > > return *const_cast<X*>(this);
> > > }
> > > };
> > >
> > > const X& tempRef=X();
> > > X& ref=tempRef.getRef();
> >
> > You don't need the const_cast -- you can get away with
> >
> > struct X
> > {
> > X& getRef() { return *this; } // non-const method
> > };
> >
> > X& ref = X().getRef(); // call non-const method on temporary
> >
> > C++ is strangely asymmetrical in that a non-const reference
> > can only be bound to a temporary as *this.
>
> Except that this doesn't extend the lifetime of the temporary, so the
> reference is immediately invalid.
(Where immediately means "at the end of the full expression",
of course.)
Truly, the getRef() method is a very dangerous thing to write.
My point (such as it was) is that this can be used in expressions
such as
bool foo(X&) {
// whatever
return true;
}
foo(X().getRef());
so the C++ type system does allow you to pass references to
temporaries via non-const reference, if the class helps.
-- James Dennett
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: brangdon@cix.co.uk (Dave Harris)
Date: Tue, 29 May 2001 19:28:14 GMT Raw View
cpdaniel@pacbell.net (Carl Daniel) wrote (abridged):
> Ugly, no?
Well, most of that machinery could be put into a standard library.
Similar stuff is already in <functional> so it is not a big stretch.
> And it requires an infinite family of little base classes
> like IntIntFun which define the parameter list/return values of
> the bound function,
Most uses will take only 0 or 1 parameters. We can cover those base
functions with just 2 templates. With a suitable library the user code
might look like:
#include <vfunctional>
int accept( std::vfun1<int,int> &f ) {
return f(5);
}
void foo() {
using std::vmem_fun1;
A a;
B b;
accept( vmem_fun1( a, &a::f ) );
accept( vmem_fun1( a, &a::g ) );
accept( vmem_fun1( b, &b::h ) );
accept( vmem_fun1( b, &b::i ) );
}
I am assuming the language will be changed to allow temporaries to be
bound to non-const arguments. Without that, accept() must take a const
argument and operator() must be const, which is an unpleasant
restriction. (This issue has already been discussed elsewhere.)
Anyway, I don't think the above is too bad. Admittedly your proposed
version was better, but the difference doesn't seem worth a language
change.
The header is a bit nasty, but not much worse than the stuff already in
<functional>. I added the "v" prefix to indicate the "virtual" operator()
in the base class. I think the difference between bound and unbound
members (as produced by std::mem_fun1()) can be deduced from the number
of arguments.
I was going to suggest an implementation, but I think it's obvious given
your code and the template for the abstract base class:
template <typename return_t, typename arg1_t>
struct vfun1 : unary_function<return_t, arg1_t> {
virtual return_t operator()( arg1_t ) = 0;
};
> AND it requires that operator() be virtual to be useful with
> non-template clients.
There has to be some polymorphism somewhere. If we're not using
templates, it must be run-time, so must involve something like a virtual
function.
> Closures fall naturally out of a solution to this problem, coupled
> with a compiler implemented way to generate anonymous local
> classes like you propose.
I tried writing an anonymous class with a forwarding function, according
to my proposal:
accept( int _fun( int x ) const { return a.f(x); } );
but it seemed less nice than what the language can already do :-(
What do you think of the Lambda Library at http://lambda.cs.utu.fi ?
We've seen several comments from committee members saying they want to be
conservative with respect to language changes at this time. They are
thinking in terms of a "type 2 technical report" rather than a full new
standard. I suspect that both of our suggestions will be rejected unless
some major problem is found with the Lambda Library approach.
I'm inclined to let it go. As Peter Dimov (more or less) says, we should
fight for typeof and binding non-const to temporaries (with implicit
conversions not allowed), and focus the rest of our efforts on library
changes.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
brangdon@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Carl Daniel" <carl@pixami.com>
Date: Tue, 29 May 2001 20:00:40 GMT Raw View
"Dave Harris" <brangdon@cix.co.uk> wrote in message
news:memo.20010529202334.31511E@brangdon.madasafish.com...
> cpdaniel@pacbell.net (Carl Daniel) wrote (abridged):
> > Ugly, no?
>
> Well, most of that machinery could be put into a standard library.
> Similar stuff is already in <functional> so it is not a big stretch.
>
[example snipped]
>
> Anyway, I don't think the above is too bad. Admittedly your proposed
> version was better, but the difference doesn't seem worth a language
> change.
>
> The header is a bit nasty, but not much worse than the stuff already in
> <functional>. I added the "v" prefix to indicate the "virtual" operator()
> in the base class. I think the difference between bound and unbound
> members (as produced by std::mem_fun1()) can be deduced from the number
> of arguments.
>
I could live with it - the need for a family of little adapter classes, even
if they are templates, feels like a hack to me, but admittedly the most
common (by far) uses are going to be functions with a few arguments only.
>
> > AND it requires that operator() be virtual to be useful with
> > non-template clients.
>
> There has to be some polymorphism somewhere. If we're not using
> templates, it must be run-time, so must involve something like a virtual
> function.
>
True enough - even a language supplied "pointer to bound function" type is
going to be (almost) equivalent to a virtual function call (slightly better
though, since no "vtable" reference would be necessary).
>
> > Closures fall naturally out of a solution to this problem, coupled
> > with a compiler implemented way to generate anonymous local
> > classes like you propose.
>
> I tried writing an anonymous class with a forwarding function, according
> to my proposal:
>
> accept( int _fun( int x ) const { return a.f(x); } );
>
> but it seemed less nice than what the language can already do :-(
>
> What do you think of the Lambda Library at http://lambda.cs.utu.fi ?
I haven't really looked at it much - I'm stuck in the world of MSVC, and if
I recall correctly, not much of the lambda library works on MSVC, so I'm not
very motiviated :)
>
> We've seen several comments from committee members saying they want to be
> conservative with respect to language changes at this time. They are
> thinking in terms of a "type 2 technical report" rather than a full new
> standard. I suspect that both of our suggestions will be rejected unless
> some major problem is found with the Lambda Library approach.
>
> I'm inclined to let it go. As Peter Dimov (more or less) says, we should
> fight for typeof and binding non-const to temporaries (with implicit
> conversions not allowed), and focus the rest of our efforts on library
> changes.
>
Agreed. Pointer-to-bound-function is a pet wish of mine - but it's not the
most important fish in the sea, for sure. My main motivation for wanting it
is that as far as I can recall, in nearly every situation where I've used
pointer-to-member-function, what I really wanted was
pointer-to-bound-function, and I had to carry around two "pointers" instead
of one. I wonder if a helpful version of "pointer-to-bound-function" could
be synthesized as a template class already - that may be a more fruitful
direction to go than pushing for a core language change.
-cd
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Carl Daniel" <cpdaniel@pacbell.net>
Date: Mon, 28 May 2001 22:54:00 GMT Raw View
"Dave Harris" <brangdon@cix.co.uk> wrote in message
news:memo.20010526185852.58045B@brangdon.madasafish.com...
> carl@pixami.com (Carl Daniel) wrote (abridged):
> > >
> > > void for_each( const int *first, const int *last, Fun &f ) {
> > > for (int *i = first; i != last; ++i)
> > > f( *i );
> > > }
> >
> > You completely missed the point.
> >
> > I do not want to call std::for_each - I want to pass a closure to my
> > own NON-Template function.
>
> Read my code again. That is not std::for_each. It is not even a template
> function. If helps, rename the function:
>
My mistake ... *checks eyes*.
Of course, we can write code like that today. A compiler implemented
mechanism for composing little classes like Fun would be helpful.
What I want to be able to do is this:
class A
{
public:
int f(int);
int g(int);
};
class B // NOT related to A
{
public:
int h(int);
int i(int);
};
int accept( magic_function_type fn)
{
return fn(5);
}
voif foo()
{
A a;
B b;
// NOT legal C++ (but I wish it were)
accept(a.*f);
accept(a.*g);
accept(b.*h);
accept(h.*i);
}
I chose .* because that's really what I want - an explicit (family of) types
which are the "return value" of operator .* and operator ->*.
I can approximate that now by defining a wrapper class:
class IntIntFun
{
public:
virtual int operator()(int) = 0;
};
template <typename T, typename Pfn> class Fun : public IntIntFun
{
T* m_p;
Pfn m_pfn;
public:
Fun(T* p,Pfn pfn) : m_p(p), m_pfn(pfn)
{
}
int operator()(int i)
{
return (m+p->*m_pfn)(i);
}
};
template <typename T, typename Pfn> makeFun(T* t, Pfn pfn)
{
return Fun<T,Pfn>(t,pfn);
}
int accept(IntIntFun pfn)
{
return (*pfn)(5);
}
void foo()
{
A a;
B b;
accept(makeFun(&a,&A::f));
accept(makeFun(&a,&A::g));
accept(makeFun(&b,&B::g));
accept(makeFun(&b,&B::h));
}
Ugly, no? And it requires an infinite family of little base classes like
IntIntFun which define the parameter list/return values of the bound
function, AND it requires that operator() be virtual to be useful with
non-template clients.
Closures fall naturally out of a solution to this problem, coupled with a
compiler implemented way to generate anonymous local classes like you
propose.
Solutions which work well with the standard algorithms are a must - but, (I
assert) the vast majority of C++ functions which are called are not standard
algorithms, nor are they templates. They're ordinary functions and member
functions of ordinary classes. Closures and pointer-to-bound function are
just as useful in that world, and a solution should not exclude or be
tailored away from that world..
-cd
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: brangdon@cix.co.uk (Dave Harris)
Date: Fri, 25 May 2001 04:55:07 GMT Raw View
qrczak@knm.org.pl (Marcin 'Qrczak' Kowalczyk) wrote (abridged):
> vector<int (*)(int)> make_adders (int n)
> {
> vector<int (*)(int)> result;
> for (int i = 0; i < n; ++i)
> {
> int adder (int x) {return i + x;}
> result.push_back (adder);
> }
> return result;
> }
You are thinking here of what is sometimes called "upward funargs". This
is something I specifically excluded from my original article. Upward
closures are fundamentally different to downward ones. For example, they
require dynamic memory allocation.
I am very uncomfortable with your code, because it does not use the
keyword "new" anywhere. How can it work if it doesn't use the heap?
> A. returns a vector of functions, each adding a different number?
> B. returns a vector of functions, each adding n?
> C. crashes?
There is only one declaration of i, so it must refer to the variable in
the for-loop. This no longer exists when make_adders() returns, so I
would expect undefined behaviour if the adders are evaluated.
OK, my expectation is a bit slanted. As you said very well earlier,
allowing people to specify whether they want references or copies will be
a bit verbose, so I think we should have a default, and I think a default
of reference will be vastly more useful and less surprising.
Here's why it is less surprising. If there is to be a copy, there must be
a call of a copy constructor. That should not be hidden. However, the
initialisation of a reference never involves a user-visible copy
constructor so it may be hidden.
Here's why it is more useful. In my experience in languages which allow
unrestricted closures, most of them fall into two kinds:
(1) Downward. These live only as long as the function which made them.
The functor passed to std::for_each() is typical.
(2) Clean. Meaning they do not reference variables from their enclosing
scope. The less() functor passed to std::map is typical. Its result will
normally depend only on its arguments.
So, I would be happy if we support clean and/or downward closures only.
Note we can still get the effect of upward closures by writing classes
explicitly. This is one advantage of being honest about the connection
between closures and classes, rather than pretending they are a kind of
function pointer. Your code would become like:
struct MyClosure {
virtual int operator()( int ) = 0;
};
struct Adder : MyClosure {
int local_i;
Adder( int i ) : MyClosure(), local_i(i) {}
virtual int operator()( int x ) {
return local_i + x;
}
};
vector<MyClosure *> make_adders( int n ) {
vector<MyClosure *> result;
for (int i = 0; i < n; ++i)
result.push_back( new Adder(i) );
return result;
}
If we must have them supported directly, I suppose your example might
look like:
struct MyClosure {
virtual int operator()( int ) = 0;
};
vector<MyClosure *> make_adders( int n ) {
vector<MyClosure *> result;
for (int i = 0; i < n; ++i) {
MyClosure *pAdder = new int __closure( int x ) :
MyClosure, int local_i(i) {
return local_i + x;
}
result.push_back( pAdder );
}
return result;
}
Here __closure is a new keyword which makes an anonymous closure. The
"new" prefix causes it to be created on the heap. The identifiers after
the colon name base classes or instance variables. The syntax here is a
bit rough; the aim is for an analogy with constructor initialiser lists.
(Later: I've now read David J. Littleboy's article, and we're thinking
along surprisingly similar lines with minor variations of syntax. The
main difference is that I want inheritance.)
Personally I think once we get into instance variables and memory
management, it will be clearer to make the class explicit.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
brangdon@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: brangdon@cix.co.uk (Dave Harris)
Date: Fri, 25 May 2001 10:40:51 GMT Raw View
carl@pixami.com (Carl Daniel) wrote (abridged):
> My concern with the implementation of closures that you propose is
> this: how do I declare a non-template function which can accept
> a closure as an argument?
By declaring a base class with a virtual function. Eg:
struct Fun {
virtual void operator()( int ) const = 0;
};
void for_each( const int *first, const int *last, Fun &f ) {
for (int *i = first; i != last; ++i)
f( *i );
}
void demo() {
int array[10]= {0};
int sum = 0;
void adder( int x ) : Fun { sum += x; }
for_each( array, array+10, adder );
cout << sum;
}
There should be no surprises here. If we are not getting static
polymorphism with templates we need dynamic polymorphism, and virtual
functions are a natural way to get it. We have to pass by reference to
avoid slicing. Inheritance works as expected, so long as we are aware we
are dealing with structs not function pointers.
> IMHO this makes this a partial solution at best, which is why I favor
> a solution which unifies closures with 'pointer-to-function'.
I think it's a full solution. Whether it is better than
http://lambda.cs.utu.fi I don't know.
> Imagine if something like this were legal:
>
> int f(int (*::*pfn)(int,int))
>
> This would be a function f which returns int and takes a parameter of
> type "pointer to bound member of unknown class taking (int,int)
> and returning int".
I think I'd want a typedef for the argument :-) I think my requirement
that the base class be named is probably an advantage. (We could have a
small library of pre-defined base classes, of course. These could be
templates.)
> The syntax could be different, of course - but something the looks
> a lot like a pointer-to-function declaration seems like the best
> choice.
Well, these are not really function pointers, are they? They must be
implemented with structs if only behind the scenes. I think making them
explicit will help the approach to scale up. For example, we can add
member functions to Fun, or declare the subclass explicitly and add
members to that.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
brangdon@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: Marcin Tustin <mt500@ugnode3.ecs.soton.ac.uk>
Date: Fri, 25 May 2001 20:13:39 GMT Raw View
Excuse my ignorance, but I can't seem to find any reference on the web to what a
closure actually is. Could anyone tell me? (Seems something to do with functors, and
hence possibly related to the mathematical concept)
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Carl Daniel" <carl@pixami.com>
Date: Fri, 25 May 2001 22:49:04 GMT Raw View
"Dave Harris" <brangdon@cix.co.uk> wrote in message
news:memo.20010525041914.59825F@brangdon.madasafish.com...
> carl@pixami.com (Carl Daniel) wrote (abridged):
> > My concern with the implementation of closures that you propose is
> > this: how do I declare a non-template function which can accept
> > a closure as an argument?
>
> By declaring a base class with a virtual function. Eg:
>
> struct Fun {
> virtual void operator()( int ) const = 0;
> };
>
> void for_each( const int *first, const int *last, Fun &f ) {
> for (int *i = first; i != last; ++i)
> f( *i );
> }
>
> void demo() {
> int array[10]= {0};
> int sum = 0;
> void adder( int x ) : Fun { sum += x; }
> for_each( array, array+10, adder );
> cout << sum;
> }
>
You completely missed the point.
I do not want to call std::for_each - I want to pass a closure to my own
NON-Template function. If closures were to be added as you propose using
anonymous local classes with operator() defined then it would be impossible
to define a non-template function which can RECEIVE a closure.
>
> > Imagine if something like this were legal:
> >
> > int f(int (*::*pfn)(int,int))
> >
> > This would be a function f which returns int and takes a parameter of
> > type "pointer to bound member of unknown class taking (int,int)
> > and returning int".
>
> I think I'd want a typedef for the argument :-) I think my requirement
> that the base class be named is probably an advantage. (We could have a
> small library of pre-defined base classes, of course. These could be
> templates.)
>
If closures derived from a standard base class and were always virtual
functions, it would work. This may be acceptable - a new "pointer to bound
function" type would be equivalent to a virutal function call no matter how
it's implemented. Unfortunately, it would mean requiring that operator() be
virtual on objects used as closures, and I don't think that'd be acceptable
to many users.
>
> > The syntax could be different, of course - but something the looks
> > a lot like a pointer-to-function declaration seems like the best
> > choice.
>
> Well, these are not really function pointers, are they? They must be
> implemented with structs if only behind the scenes. I think making them
> explicit will help the approach to scale up. For example, we can add
> member functions to Fun, or declare the subclass explicitly and add
> members to that.
>
No, they're not really function pointers - they're really a combination of
an object pointer AND a function pointer. It would definitely be nice to be
able to pass any member function with a suitable parameter list through a
"pointer to bound member". I guess that's yet another syntax to come up
with...
I think a key difficulty to deal with is that in order for closures to be
useful with non-template clients (functions which receive & call closures),
then it's essential that the type of a closure does NOT mention the type of
the object on which it's called.
-cd
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: remove.haberg@matematik.su.se (Hans Aberg)
Date: Fri, 25 May 2001 23:21:01 GMT Raw View
In article <3b0eab20@news.ecs.soton.ac.uk>, mt500@ecs.soton.ac.uk wrote:
>Excuse my ignorance, but I can't seem to find any reference on the web to
what >a closure actually is. Could anyone tell me? (Seems something to do
with >functors, and hence possibly related to the mathematical concept)
In the book by David A. Schmidt, "Denotational Semantics", 10.3.1, a
_closure_ is used to denote an expression which has not been reduced (or
evaluated).
Since this is with respect to an evaluator, a closure is simply a piece of
code data that later can be evaluated.
In math, a closure of a set S with respect to a set of operations O is
simply what is be generated when applying those operations in O a finite
number of times to S. There is some similarity with the CS "covers" in
that the latter are usually what is generated by a set of operations that
can be evaluated.
I think (if not my memory fails me) that closures also can be called
function covers.
It is easy to build closures in C++:
First build a polymorphic hierarchy: A class "object" holding a
polymorphic pointer which points to objects of classes all derived from a
class object_root. Then add an evaluator to those classes:
class object;
class object_root {
public:
virtual object evaluate(const object&) const = 0;
};
class object {
mutable object_root* value_;
public:
object(object_root* op = 0) : value_ { }
object operator()(const object& x) const {
if (value_ == 0) // error
return value_->evaluate(x);
}
};
You can then add derived classes of object_root representing various
computer operations, which then can be evaluated using object::operator().
Hans Aberg * Anti-spam: remove "remove." from email address.
* Email: Hans Aberg <remove.haberg@member.ams.org>
* Home Page: <http://www.matematik.su.se/~haberg/>
* AMS member listing: <http://www.ams.org/cml/>
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: brangdon@cix.co.uk (Dave Harris)
Date: Sun, 27 May 2001 02:59:59 GMT Raw View
carl@pixami.com (Carl Daniel) wrote (abridged):
> >
> > void for_each( const int *first, const int *last, Fun &f ) {
> > for (int *i = first; i != last; ++i)
> > f( *i );
> > }
>
> You completely missed the point.
>
> I do not want to call std::for_each - I want to pass a closure to my
> own NON-Template function.
Read my code again. That is not std::for_each. It is not even a template
function. If helps, rename the function:
struct Fun {
virtual void operator()( int ) const = 0;
};
// Note: *NOT* a template! Only works with ints.
void some_function( const int *first, const int *last, Fun &f ) {
for (int *i = first; i != last; ++i)
f( *i );
}
void demo() {
int array[10]= {0};
int sum = 0;
void adder( int x ) : Fun { sum += x; }
some_function( array, array+10, adder );
cout << sum;
}
This is passing a closure to a user-defined non-template function.
> If closures derived from a standard base class and were always virtual
> functions, it would work.
I wouldn't want to impose that overhead where it wasn't needed. For the
common uses, with std algorithms, it isn't needed. I'd prefer to make the
inheritance explicit, so that users can request it when they want it. (Or
can the overhead be optimised away as the template is instantiated?)
Having a set of standard base classes, which we could use if and when we
wanted, would be a good thing, of course.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
brangdon@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: pdimov@mmltd.net (Peter Dimov)
Date: Thu, 24 May 2001 19:08:44 GMT Raw View
jk@steel.orel.ru (Eugene Karpachov) wrote in message news:<slrn9gnvfs.rm.jk@localhost.localdomain>...
> Wed, 23 May 2001 15:08:20 GMT Peter Dimov :
> >Using http://lambda.cs.utu.fi, you can write:
>
> [some nice-looking code]
>
> But you cannot write (with the library approach) something equivalent to
>
> lambda(x): std::cout << x->first << ' ' << x->second ;
Actually I can, but it's quite ugly; this is the second drawback of
this approach, operator-> can't be made to work. But the library can
easily be extended to handle common situations (std::pair), and public
data members are rare.
--
Peter Dimov
Multi Media Ltd.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "David J. Littleboy" <davidjl@gol.com>
Date: Thu, 24 May 2001 19:43:09 GMT Raw View
"Marcin 'Qrczak' Kowalczyk" <qrczak@knm.org.pl> wrote:
>>>>>>>>>>>>>>>>>>>>>>>>>>>
> I think that programmers would be surprised if modification of these
> variables inside and outside the closure were independent, so we must
> have references.
I withdraw this claim:
vector<int (*)(int)> make_adders (int n)
{
vector<int (*)(int)> result;
for (int i = 0; i < n; ++i)
{
int adder (int x) {return i + x;}
result.push_back (adder);
}
return result;
}
Would you expect that this function:
A. returns a vector of functions, each adding a different number?
B. returns a vector of functions, each adding n?
C. crashes?
I vote for A.
<<<<<<<<<<<<<<<<<<<<<<<<<<<
I vote for C. In C++, the _destructors_ of local objects are called on block
exit. Therefore, functions that reference local variables in their lexical
scope and are passed out of that scope are incompatible with the semantics
of C++. C++ isn't a functional language, and trying to make it one is
unreasonable. However, given that C++ objects already have operator(), and
the standard library makes use of that, it's clear that some addition syntax
would be nice. (Note that the _type_ of your "adder" closure is problematic:
it's more than just a function that takes one int and returns int.)
I'd like the following syntax. A lambda expression creates a temporary
object of an anonymous local type appropriate for use as a template
argument. A lambda expression consists of the (new) keyword lambda, the
return type in angle brackets, an argument list, an optional colon plus
list of member objects with initializers, and a body. A variable from an
enclosing lexical scope that appears in the body is treated as a reference
to that variable. It is illegal (undefined behavior) to pass a lambda body
outside a scope that defines a local variable referenced in that lambda.
Stroustrup's example from page 288 would then look like:
for_each(aa.begin(), aa.end(),
lambda<void> (complex& c) const : val(z)
{ c += val; });
It's a lot easier than defining the Add object and you see the definition of
the operation at the point in the code where it's used. If you need to reuse
this function, then you would use normal C++ syntax. lambda is (mostly) just
a shorthand, although the ability to reference local variables is a major
change that will make life difficult for implementers.
Actually, since this is example won't be passing copies of this object up,
it doesn't need the member object, and could be simply written:
for_each(aa.begin(), aa.end(),
lambda<void> (complex& c)
{ c += z; });
>If somebody wants to obtain B and thus avoid copying
>large data, he should use a pointer or reference. If he only wants
>to pass the closure down, it may be a raw reference or pointer; if
>he wants to return it, it must be a smart pointer; in this case data
>isn't large so copying an int by value is the right choice.
As I mentioned above, the type is a problem. Although C++ has _pointer to
functions_, it doesn't really have functions as a data type (assuming I've
got this right; someone correct me if I'm wrong here): function objects are
instances of user defined types that happen to have operator() defined.
Therefore, lambda would largely be limited to use with the standard library
(or other templates) unless someone did the work to add a new data type to
C++. (Alternatively, lambda plus typeof might be interesting.)
David J. Littleboy
davidjl@gol.com
Tokyo, Japan
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Marcin 'Qrczak' Kowalczyk" <qrczak@knm.org.pl>
Date: Thu, 24 May 2001 20:37:36 GMT Raw View
Thu, 24 May 2001 12:43:48 GMT, Marcin 'Qrczak' Kowalczyk <qrczak@knm.org.=
pl> pisze:
> A. returns a vector of functions, each adding a different number?
> Python works this way.
Oops, I was wrong: it doesn't. Rebinding of local variables from outer
scopes is immediately visible in inner scopes, i.e. they are put in
a closure by reference, not by value. Sorry for the last posting.
Since this is the only choice consistent with treatment of global
variables (they are not physically put in a closure at all), I expect
it to be true in hypothetic C++0x with closures. This provides no
solution for returning a closure which refers to local data :-(
--=20
__("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
\__/
^^ SYGNATURA ZAST=CAPCZA
QRCZAK
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: brangdon@cix.co.uk (Dave Harris)
Date: Thu, 24 May 2001 21:34:27 GMT Raw View
pdimov@mmltd.net (Peter Dimov) wrote (abridged):
> Using http://lambda.cs.utu.fi
Thanks. I was vague aware of the expression stuff, but didn't have a good
reference.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
brangdon@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: pdimov@mmltd.net (Peter Dimov)
Date: Wed, 23 May 2001 15:08:20 GMT Raw View
brangdon@cix.co.uk (Dave Harris) wrote in message news:<memo.20010522203643.61245A@brangdon.madasafish.com>...
[...]
> A third step might be to provide a more concise syntax for the struct.
> This is where it gets tricky.
>
> void demo4() {
> int i = 1;
> int closure4( int x ) { return x+i; }
> int array[10] = {0};
>
> std::transform( array, array+10, array, closure4 );
> }
>
> where the type of closure4 is a struct like Closure1, although anonymous.
Using http://lambda.cs.utu.fi, you can write:
void demo4()
{
int i = 1;
int array[10] = { 0 };
std::transform(array, array + 10, array, free1 + i);
// std::for_each(array, array + 10, free1 += i);
// std::for_each(array, array + 10, std::cout << free1 << '\n');
}
The library approach has its shortcomings, since f(free1) can't be
'hijacked' without making f 'lambda-aware' - you have to write bind(f,
free1), but it's available now. With a little help from the language
(typeof and the 'T& can't bind to rvalues' problem) it has the
potential to become even more attractive.
--
Peter Dimov
Multi Media Ltd.
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Carl Daniel" <carl@pixami.com>
Date: Wed, 23 May 2001 15:30:40 GMT Raw View
"Dave Harris" <brangdon@cix.co.uk> wrote in message
news:memo.20010522203643.61245A@brangdon.madasafish.com...
> qrczak@knm.org.pl (Marcin 'Qrczak' Kowalczyk) wrote (abridged):
[snipped]
> This is different to Carl Daniel's approach in that it does not invent
> any new kinds of types. Nor am I supposing that stack frames are objects
> (currently they are not). The struct needs external linkage and we need
> to be able to use them as template parameters. This means it must be
> added to the nearest global or namespace scope, like template
> instantiations. The object should not be added to that scope. (See C++PL
> $C.13.8.3 for some discussion of these concerns.)
My concern with the implementation of closures that you propose is this:
how do I declare a non-template function which can accept a closure as an
argument? With your proposal, I basically can't, since the closure is an
instance of an anonymous local class. IMHO this makes this a partial
solution at best, which is why I favor a solution which unifies closures
with 'pointer-to-function'. Whether that solution also unifies closures
with 'pointer-to-bound-function' is a separate issue, but IMO, a solution
which unifies all three would be an elegant addition to the language. A
combination of the different approaches is possible too: if at the call
site, closures appeared as you propose, but at the callee site, the could be
declared as something like a pointer-to-function, we'd have a very useful
combination.
Imagine if something like this were legal:
int f(int (*::*pfn)(int,int))
This would be a function f which returns int and takes a parameter of type
"pointer to bound member of unknown class taking (int,int) and returning
int". The syntax could be different, of course - but something the looks a
lot like a pointer-to-function declaration seems like the best choice.
-cd
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Marcin 'Qrczak' Kowalczyk" <qrczak@knm.org.pl>
Date: Wed, 23 May 2001 17:20:45 GMT Raw View
Wed, 23 May 2001 09:30:14 GMT, Dave Harris <brangdon@cix.co.uk> pisze:
> Oh, please don't say that! STL algorithms will never be pleasant to use=
=20
> without better support for closures, and I suspect garbage collection=20
> will never be made mandatory in C++, so if we link closures to GC we're=
=20
> doomed.
I agree that we are doomed. It gets worse with time: programming
tasks are more ambitious so C++ is getting more anachronic with its
low level approach.
> C++ has plenty of situations where an outstanding reference to
> an object does not keep that object alive. Why should closures
> be different?
Ok, assume lack of GC. Should a closure object contain references to
free variables or copies of them?
I think that programmers would be surprised if modification of these
variables inside and outside the closure were independent, so we must
have references.
But it completely disallows returning closures by functions, storing
them in data structures for longer etc. No dynamic allocation, no
smart pointers can help. There is no way to refer to a non-global
object inside the closure such that it works after the function which
syntactically contains the closure creation expression exits.
You can't have a wrapper which returns a particular form of a closure,
but must always write the full closure creation expression at the
point it is used (and such expression is not going to be small and
simple with C++ syntax).
Other expressions which return functions can't use the same type as
closures, but must define custom function objects. So we lose a benefit
of real closures that their types depend only on the interface (types
of arguments and result).
C++ relies on copying by value (e.g. to return values from functions).
It's usually natural to pass by reference. The only hope I see in
keeping the rules of C++ and allowing some higher level programming
is using smart pointers, which can be freely copied but don't cause
physical copying of the pointed object. It's not as efficient as real
GC but C++ is not going to have real GC.
Smart pointers allow to pass objects by reference without manual
freeing; it's a shame that standard C++ doesn't provide a smart pointer
(copyable!). Unfortunately it doesn't help closures if they store
a reference to the smart pointer...
Copying a smart pointer is quite cheap. I want to copy it to the
closure, because the original pointer will be lost, and the smart
pointer is used precisely to avoid copying of the whole object.
It's even cheaper to refer to the object directly from the copied
smart pointer than to find the pointer in a stack frame pointed to
from the closure payload. It's annoying that the correct behavior is
so close yet out of reach!
It seems that neither automatic making references nor automatic
making copies is good. And the problem is that allowing or requiring
to specify manually how to pass free variables adds more complexity
to this already horribly complex language, and more syntactic baggage
to using closures.
I don't know if passing by reference by default and adding some way
to specify passing by value would work well. Perhaps this is the
only solution...
There is still a problem that a closure requires an amount of
memory which is not deducible from its type, which requires dynamic
allocation, which is slow in C++ and currently rarely implicit.
--=20
__("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
\__/
^^ SYGNATURA ZAST=CAPCZA
QRCZAK
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: jk@steel.orel.ru (Eugene Karpachov)
Date: Wed, 23 May 2001 18:20:28 GMT Raw View
Wed, 23 May 2001 09:30:14 GMT Dave Harris wrote:
> void demo2() {
> int i = 1;
>
> struct Closure2 {
> int &ref_i;
> Closure2( int &i ) : ref_i(i) {
> }
> int operator()( int x ) {
> return x + ref_i;
> }
> } closure2(i);
>
> int array[10] = {0};
> std::transform( array, array+10, array, closure2 );
> }
>
>This is currently not permitted. One reason is that the template argument
>must have extern linkage (see the standard, $14.6.4.1), which the local
>class doesn't have. (Stroustrup also makes some comments in C++PL
>$C.13.8.3, about templates ignoring local names, but I don't think he is
>talking about the same issue.) It would seem we need a way to give local
>classes external linkage.
A very obvious way: put them into anonymous namespace, like:
namespace {
namespace <mangled demo2> {
struct Closure2 {
...
};
}
}
--
jk
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: jk@steel.orel.ru (Eugene Karpachov)
Date: Wed, 23 May 2001 18:20:35 GMT Raw View
Wed, 23 May 2001 15:08:20 GMT Peter Dimov =CE=C1=D0=C9=D3=C1=CC:
>Using http://lambda.cs.utu.fi, you can write:
[some nice-looking code]
>The library approach has its shortcomings, since f(free1) can't be
>'hijacked' without making f 'lambda-aware' - you have to write bind(f,
>free1), but it's available now. With a little help from the language
>(typeof and the 'T& can't bind to rvalues' problem) it has the
>potential to become even more attractive.
But you cannot write (with the library approach) something equivalent to
lambda(x): std::cout << x->first << ' ' << x->second ;
--=20
jk
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: "Marcin 'Qrczak' Kowalczyk" <qrczak@knm.org.pl>
Date: Thu, 24 May 2001 12:43:48 GMT Raw View
Wed, 23 May 2001 17:20:45 GMT, Marcin 'Qrczak' Kowalczyk <qrczak@knm.org.=
pl> pisze:
> I think that programmers would be surprised if modification of these
> variables inside and outside the closure were independent, so we must
> have references.
I withdraw this claim:
vector<int (*)(int)> make_adders (int n)
{
vector<int (*)(int)> result;
for (int i =3D 0; i < n; ++i)
{
int adder (int x) {return i + x;}
result.push_back (adder);
}
return result;
}
Would you expect that this function:
A. returns a vector of functions, each adding a different number?
B. returns a vector of functions, each adding n?
C. crashes?
I vote for A. If somebody wants to obtain B and thus avoid copying
large data, he should use a pointer or reference. If he only wants
to pass the closure down, it may be a raw reference or pointer; if
he wants to return it, it must be a smart pointer; in this case data
isn't large so copying an int by value is the right choice.
Python works this way. OTOH Lisp, Scheme and Ruby embed references
in closures. Functional languages usually don't have this dilemma
because variables are immutable.
--=20
__("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
\__/
^^ SYGNATURA ZAST=CAPCZA
QRCZAK
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]
Author: brangdon@cix.co.uk (Dave Harris)
Date: Wed, 23 May 2001 09:30:14 GMT Raw View
qrczak@knm.org.pl (Marcin 'Qrczak' Kowalczyk) wrote (abridged):
> It will never work well without garbage collection. A function closure
> referring to an object must keep it alive, and in general it's not
> statically known when the object is not referred to anymore.
Oh, please don't say that! STL algorithms will never be pleasant to use
without better support for closures, and I suspect garbage collection
will never be made mandatory in C++, so if we link closures to GC we're
doomed.
Anyway, I don't think it follows. C++ has plenty of situations where an
outstanding reference to an object does not keep that object alive. Why
should closures be different? We can already write things like:
struct Closure1 {
int &ref_i;
Closure1( int &i ) : ref_i(i) {
}
int operator()( int x ) {
return x + ref_i;
}
};
void demo1() {
int i = 1;
int array[10] = {0};
std::transform( array, array+10, array, Closure1(i) );
}
which captures a reference to a stack variable. If the closure outlived
the demo's stack frame, we would get into undefined behaviour, but we can
live with that. We just have to be careful. In practice most closures
will be used with standard algorithms which do not capture them; they
will be "downward funargs".
We could try approaching this closure thing in steps. The first step
might be to permit templates to be specialised on local classes, eg:
void demo2() {
int i = 1;
struct Closure2 {
int &ref_i;
Closure2( int &i ) : ref_i(i) {
}
int operator()( int x ) {
return x + ref_i;
}
} closure2(i);
int array[10] = {0};
std::transform( array, array+10, array, closure2 );
}
This is currently not permitted. One reason is that the template argument
must have extern linkage (see the standard, $14.6.4.1), which the local
class doesn't have. (Stroustrup also makes some comments in C++PL
$C.13.8.3, about templates ignoring local names, but I don't think he is
talking about the same issue.) It would seem we need a way to give local
classes external linkage.
A second step might be to allow the local class to access variables from
the surrounding scope. Eg:
void demo3() {
int i = 1;
struct Closure3 {
int operator()( int x ) {
return x + i;
}
} closure3;
int array[10] = {0};
std::transform( array, array+10, array, closure3 );
}
This would compile to code similar to the Closure2 example.
A third step might be to provide a more concise syntax for the struct.
This is where it gets tricky.
void demo4() {
int i = 1;
int closure4( int x ) { return x+i; }
int array[10] = {0};
std::transform( array, array+10, array, closure4 );
}
where the type of closure4 is a struct like Closure1, although anonymous.
Is this adequate? Here's what we've gained over the original:
(1) No need to declare ref_i.
(2) No need to initialise ref_i.
(3) Therefore, no need for a constructor at all.
(4) Therefore, no need to name the struct.
(5) No need to specify operator().
We still need to specify the type of arguments and its result, a name for
the object, and of course the code that implements it. I feel this
eliminates most of the unnecessary bureaucratic overhead but keeps the
essentials. I prefer it to the original Closure1.
Now compare with the non-algorithm version:
void demo5() {
int i = 1;
int array[10] = {0};
for (int *p = array; p != array+10; ++p)
*p += i;
}
I think they are roughly equal. The non-algorithm version is simple and
direct, and slightly shorter. Really the most I would say is that the
closure-based algorithm version is now competitive. The original Closure1
version, which needed 7 or so extra lines of code, wasn't. I would still
like to see more improvements on the algorithm side, as mentioned in
other threads. Eg:
void print6( const std::deque<int> &v, const char *separator ) {
void print_one( int x ) {
cout << x << separator;
}
std::for_each( iseq(v), print_one );
cout << endl;
}
(where iseq() comes from C++PL $18.3.1) is probably an improvement over:
typedef std::deque<int>::const_iterator const_iterator;
void print7( const std::deque<int> &v, const char *separator ) {
for (const_iterator i = v.begin(); i != v.end(); ++i)
cout << *i << separator;
cout << endl;
}
especially if I think that for_each() might be specialised for deque
iterators.
A fourth step is to permit the closure to be declared at the point of
use.
void demo8() {
int i = 1;
int array[10] = {0};
std::transform( array, array+10, array, int __closure( int x ) {
return x+i;
} );
}
void print9( const std::deque<int> &v, const char *separator ) {
std::for_each( iseq(v), void __closure( int x ) {
cout << x << separator;
} );
cout << endl;
}
This avoids the need to name the closure object at all.
However, I find this quite hairy. There really seems to be a lot going on
in those function calls. All those type declarations. The languages I
know which use closures well are either dynamically typed (like
Smalltalk), or else use type inference a lot (like Haskell). Java is both
statically and manifestly typed, and although it permits anonymous
classes they are really rather awkward. I am not at all sure these
improve over print6() and demo4().
It gets worse if we add exception throw clauses and inheritance, and
virtual (or the proposed "override" keyword). To me, this:
class Visitor;
void demo10( const Tree &tree, const char *separator ) {
tree.accept( override void __closure( int x )
throw() : public Visitor {
cout << x << separator;
} );
cout << endl;
}
is too much of a good thing. But maybe I am just not used to it yet, or
picked a bad syntax. I find:
class Visitor;
void demo11( const Tree &tree, const char *separator ) {
override void visitor( int x ) throw() : public Visitor {
cout << x << separator;
}
tree.accept( visitor );
cout << endl;
}
relatively clear. If anything, having to name the object helps document
what is going on.
Just to be explicit, the "visitor" in demo11() would be short-hand for
something like:
struct __anonymous_visitor12 : public Visitor {
const char *&separator;
__anonymous_visitor12( const char *&separator_ ) :
separator(separator_) {}
override void operator()( int x ) throw() {
cout << x << separator;
}
};
__anonymous_visitor12 visitor;
This is different to Carl Daniel's approach in that it does not invent
any new kinds of types. Nor am I supposing that stack frames are objects
(currently they are not). The struct needs external linkage and we need
to be able to use them as template parameters. This means it must be
added to the nearest global or namespace scope, like template
instantiations. The object should not be added to that scope. (See C++PL
$C.13.8.3 for some discussion of these concerns.)
The struct is anonymous and so cannot be a friend, and functions cannot
be overloaded on it. However, it can inherit and functions can be
overloaded on the base class. Inheritance is a powerfool tool here.
The struct cannot have any instance variables of its own (other than the
implicitly declared references to variables in enclosing scopes).
However, it can inherit instance variables. Also, variables added to the
enclosing scope can be used like instance variables. Here's another
example to show this:
int sum13( const int *first, const int *last ) {
int sum = 0;
int summate( int x ) { sum += x; }
std::for_each( first, last, summate );
return sum;
}
In summary, I think we can hope to find a proposal for closures which
does not require garbage collection or type inference. It may mean a
bigger language change than, eg, typeof, but I don't see much point in
improving support for algorithm writers unless we do something to make
algorithms themselves more usable.
Dave Harris, Nottingham, UK | "Weave a circle round him thrice,
brangdon@cix.co.uk | And close your eyes with holy dread,
| For he on honey dew hath fed
http://www.bhresearch.co.uk/ | And drunk the milk of Paradise."
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html ]