Topic: closures
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/03/08 Raw View
Andrei Alexandrescu <andrewalex@hotmail.com> posts a brief critique
of the closure-on-this implementation I posted. I'm trying to keep the
quoting to a minimum in this reply; if you need the original articles to
follow this, they're at
<http://www.dejanews.com/[ST_rn=ps]/getdoc.xp?AN=451319782>
and <http://www.dejanews.com/[ST_rn=ps]/getdoc.xp?AN=451412807>.
Andrei asks:
> Why didn't you make a step forward to bind to
> regular functions as well?
Mostly in the interests of simplicity. I think that ability can be
added in a straightforward way. I do have reservations about such
bindings in the context of a possible language feature -- they may
significantly complicate the call site. But in this template, the
call site is already ridiculously complicated; the increase would
be comparatively small.
I used a single function-pointer-type parameterto describe the parameter
list and return value of the template:
>> template < class Signature > class BoundMethod
>> { /* private, undefined copy and assignment */ };
>>
>> template < class R, class P1, class P2 >
>> class BoundMethod< R(*)(P1,P2) >
And Andrei asked:
> Why not:
>
> template < class R, class P1 = void, class P2 = void>
> class BoundMethod { ... };
In an earlier incarnation of this code, I would write BoundMethod<A,B,C>,
and I was never sure whether A or C was the return type. When writing
BoundMethod< A(*)(B,C) > I don't think that will happen. As you point
out, this comes at the expense of people less familiar with the
more esoteric declaration syntax.
I can't say I see what you're getting at with the void default parameters.
Since they would be used to form an illegal parameter list (void,void),
it seems they could only lead to errors.
(Biju Thomas questions whether this sort of partial specialization is
allowed. I can only answer that I don't see why not, and that CodeWarrior
can handle it. Does anyone want to provide a more thorough analysis?)
While my constructor was templated on only one parameter
<class ObjectType>, Andrei proposed a constructor templated on two
parameters:
> template <class ObjectType, class MethodType>
> BoundMethod(ObjectType & Obj, MethodType M)
> : object( const_cast<void *>(
> static_cast<const void *>( &Obj ) ) ),
> member( reinterpret_cast< GenericMember >( theMember ) ),
> call( &Call<ObjectType, MethodType> )
> {
> static_checker(sizeof(MethodType) <= sizeof(GenericMember));
> }
Since MethodType is entirely determined by ObjectType, I think the
two amount to the same thing. As I see it, the trade-off is between
having to think harder about argument deduction when writing the one-
parameter version, and pushing the errors involving mismatched
parameters into the two-parameter Call template. In my estimation,
a tossup.
As for checking the size: I was programming to the standard, which
by my reading allows the sizes to differ, but still requires the
reinterpret_casts to work. The report on c.l.c++.m that in VC++ the
sizes actually _do_ differ, and suggesting that therefore the casts
cannot work, just reinforces my long-held belief that it's difficult
for a compiler to be compatible with both C++ and VC++.
To these criticisms, I can only plead incompetence:
> It doesn't allow binding parms to fixed values.
> It doesn't work with STL.
I also criticized my implementation:
>> It doesn't allow conversions, even for covariant return types,
>> so it gets no bonus points.
To which Andrei replied:
> The templatized version above does it. What about my bonus points?
To be clear: the conversion I'm talking about would be from
BoundMethod< R1(*)(parameters) > to BoundMethod< R2(*)(same parameters) >,
provided that a conversion from R1 to R2 exists. (Subject to the same
restrictions as covariant overriding, I think this could be easily
provided by a compiler.) I don't see how to write such a conversion
operator for either template.
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Biju Thomas <bijuthom@ibm.net>
Date: 1999/03/05 Raw View
Lisa Lippincott wrote:
>
> template < class Signature > class BoundMethod
> { /* private, undefined copy and assignment */ };
>
> template < class R, class P1, class P2 >
> class BoundMethod< R(*)(P1,P2) >
> {
[...]
Is it legal for a partial specialization to have more template
parameters than the primary template?
I could not find anything in the standard specific about it. I was
trying to see if the above technique works (which will be quite useful
for me in similar contexts) and my compiler chokes - when I use the
above class, it says that too many template arguments are specified for
the class.
I have a feeling that the compiler is correct, but not sure.
--
Best regards,
Biju Thomas
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/03/04 Raw View
Thanks to Andrei Alexandrescu and those who posted to the threads he's
started on comp.lang.c++.moderated, I've been able to put together
an implementation of closures on the this-pointer with which I'm happier.
This implementation won't fail for lack of memory and yet has ordinary
value semantics. In fact, except for having a constructor, it's a POD.
The basic hacks involved are:
-- specializing a template according to the number of parameters,
-- storing the object pointer in a void *,
-- reinterpret_casting pointers to member functions,
-- using a pointer-to-function to indirectly dispatch the call.
In the name of brevity, I'll only give the two-parameter specialization:
template < class Signature > class BoundMethod
{ /* private, undefined copy and assignment */ };
template < class R, class P1, class P2 >
class BoundMethod< R(*)(P1,P2) >
{
private:
class Generic; // undefined
typedef R (Generic::*GenericMember)(P1,P2);
void *object;
GenericMember member;
R (*call)( void *, GenericMember, P1, P2 );
template < class ObjectType >
static R Call( void *object, GenericMember member, P1 p1, P2 p2 )
{
( static_cast< ObjectType * >( object )
->* reinterpret_cast< R (ObjectType::*)(P1,P2) >( member ) )
(p1, p2);
}
public:
template < class ObjectType >
BoundMethod( ObjectType& theObject,
R (ObjectType::*theMember)(P1, P2) )
: object( const_cast<void *>(
static_cast<const void *>( &theObject ) ) ),
member( reinterpret_cast< GenericMember >( theMember ) ),
call( &Call<ObjectType> )
{}
R operator()( P1 p1, P2 p2 ) const
{
return (*call)( object, member, p1, p2 );
}
};
I'm still a little shaky on function template argument deduction, but
I think that this should work for all valid combinations of cv-qualifiers
on the object and pointer-to-member, and reject the invalid ones. If
someone wants to check this, keep in mind that there's undefined
behavior if the reinterpret_cast in Call does not return the pointer
to the exact type passed to the constructor.
Relative to the wish-list I posted earlier, it has these problems:
In calling a closure, it creates excess temporaries which cannot
be optimized away unless the as-if rule applies. It may run into
access problems in creating these temporaries, which would prevent
the template instantiation.
It doesn't deal with exception-specifications.
It puts a limit on the number of parameters, but, by painful
repetition, the limit can be raised arbitrarily high.
It can't statically bind to a virtual member. (That is,
it can't create the closure for object.qualifier::name, and
it applies the virtual mechanism as it stands at call-time,
rather than when the closure is created.)
It doesn't allow conversions, even for covariant return types,
so it gets no bonus points.
It adds this practical problem: no compiler I use can handle it,
so I'm posting this without immediate plans to try it out.
--Lisa Lippincott
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <andrewalex@hotmail.com>
Date: 1999/03/05 Raw View
Lisa Lippincott wrote in message ...
[snip]
>This implementation won't fail for lack of memory and yet has
ordinary
>value semantics. In fact, except for having a constructor, it's a
POD.
>The basic hacks involved are:
> -- specializing a template according to the number of parameters,
> -- storing the object pointer in a void *,
> -- reinterpret_casting pointers to member functions,
> -- using a pointer-to-function to indirectly dispatch the call.
Sounds portable to me. Why didn't you make a step forward to bind to
regular functions as well?
>template < class Signature > class BoundMethod
> { /* private, undefined copy and assignment */ };
>
>template < class R, class P1, class P2 >
>class BoundMethod< R(*)(P1,P2) >
Why not:
template < class R, class P1 = void, class P2 = void>
class BoundMethod { ... };
> {
> private:
> class Generic; // undefined
> typedef R (Generic::*GenericMember)(P1,P2);
This is portable, according to what the guys on clcm told us all.
> public:
> template < class ObjectType >
> BoundMethod( ObjectType& theObject,
> R (ObjectType::*theMember)(P1, P2) )
> : object( const_cast<void *>(
> static_cast<const void *>( &theObject ) ) ),
> member( reinterpret_cast< GenericMember >( theMember ) ),
> call( &Call<ObjectType> )
> {}
What about:
template <class ObjectType, class MethodType>
BoundMethod(ObjectType & Obj, MethodType M)
: object( const_cast<void *>(
static_cast<const void *>( &Obj ) ) ),
member( reinterpret_cast< GenericMember >( theMember ) ),
call( &Call<ObjectType, MethodType> )
{
static_checker(sizeof(MethodType) <= sizeof(GenericMember));
}
Similar in the call. You getaway with simpler code and easier
scalability towards many parameters. Also the template arg list I
propose above (instead of the pointer-to-function template arg) with
default members scales MUCH better to multiple args. To say nothing
about much more elegant client code syntax.
>I'm still a little shaky on function template argument deduction, but
>I think that this should work for all valid combinations of
cv-qualifiers
>on the object and pointer-to-member, and reject the invalid ones.
The template version works cleanly.
>someone wants to check this, keep in mind that there's undefined
>behavior if the reinterpret_cast in Call does not return the pointer
>to the exact type passed to the constructor.
Someone on clcm quoted from the standard that a pointer to a method of
a class can be reinterpret_cast'ed to a pointer to a method of another
class and back. So it seems like it's okay.
>Relative to the wish-list I posted earlier, it has these problems:
>
> In calling a closure, it creates excess temporaries which
cannot
> be optimized away unless the as-if rule applies. It may run
into
> access problems in creating these temporaries, which would
prevent
> the template instantiation.
>
> It doesn't deal with exception-specifications.
>
> It puts a limit on the number of parameters, but, by painful
> repetition, the limit can be raised arbitrarily high.
It makes client syntax a mess - they have to know how an anonymous
declaration of a pointer to function looks like.
It doesn't bind to regular functions, so it locks me in the object
paradigm.
It doesn't allow binding parms to fixed values.
It doesn't work with STL.
(Sorry to be that corrosive.)
> It doesn't allow conversions, even for covariant return types,
> so it gets no bonus points.
The templatized version above does it. What about my bonus points?
>It adds this practical problem: no compiler I use can handle it,
>so I'm posting this without immediate plans to try it out.
Should be free lunch for Inprise Borland C++ 4.0. That's a nice
product.
I managed to break BC4 only with the curry method. Still imperfect.
Until I won't implement curry() I won't post my code.
Andrei
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Dave Abrahams" <abrahams@mediaone.net>
Date: 1999/02/27 Raw View
In article <36ceab04.5280698@news.freeserve.net> ,
John_Maddock@compuserve.com (John Maddock) wrote:
> Even if a general template case were available, it still does not
> address one key point: that statements of the type:
>
> myObjectPtr->memberFunction;
>
> Automatically return a type "closure".
Hey, listen: I don't want to be a dour stick-in-the-mud. This would
obviously be an aesthetically pleasing (and to some, highly useful)
extension to the C++ language.
The name "closure" already does have many well-accepted meanings in computer
science. The term "unbound" expresses the opposite of what this beast means;
I vote for "bound member function" is the most descriptive term.
Not even Lisa Lippincott, who is so well spoken, has managed to illustrate
for me just how it provides a real advantage to a programmer, but I have to
concede that supporting different approaches which make some people more
comfortable is desirable in itself. I'd still love to hear a good
explanation of what bound member functions really buy you. I might start
using the idiom myself.
I still believe that for most people and situations, special language
support is unneccessary, and I hope Andrei Alexandrescu will post his
solution soon. He's obviously demonstrated the template chops to do it.
Since the language standard is in a "stability" phase right now, we're not
going to see any changes for a few years anyway.
-Dave
P.S. Users of Python might notice a certain resonance. See <
http://www.python.org/doc/tut/methodObjects.html>
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1999/02/28 Raw View
Dave Abrahams wrote in message ...
[snip]
This is a short errata to my earlier posting:
1. Please read egcs instead of agcs
2. I didn't know of Jean Louis Leroy's implementation when I wrote
mine (my previous posting hints otherwise). This is sad as I thought
I'm the only nutty kid on the block :o).
Andrei
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1999/02/28 Raw View
Dave Abrahams wrote in message ...
[snip]
>Not even Lisa Lippincott, who is so well spoken, has managed to illustrate
>for me just how it provides a real advantage to a programmer, but I have to
>concede that supporting different approaches which make some people more
>comfortable is desirable in itself. I'd still love to hear a good
>explanation of what bound member functions really buy you. I might start
>using the idiom myself.
There is a great explanation: the Command pattern in the GoF book.
It's actually *all about closures*. If you have generic closures,
implementing the Command pattern in any context you want is a snap.
Having generic closures implemented just transforms this pattern from
a recipe (do this and do that) to a codebase that can be found in a
library. Of course things still have to be done, but there's some
plumbing to start with.
>I still believe that for most people and situations, special language
>support is unneccessary, and I hope Andrei Alexandrescu will post his
>solution soon. He's obviously demonstrated the template chops to do
it.
Sorry to tantalize everyone for next to nothing. My implementation
generalizes Jean-Louis Leroy's. It adds multiple parms (up to a fixed
limit), functors of any type (including pointers to global functions)
and adds currying and binding as discussed. It's not rocket science.
Sorry if I touted it - I wanted the free dinner :o).
The problem is, I've reached compiler limits. MSVC and agcs (mingw)
are all what I have, and they cannot compile the code. BTW, I've
discovered that they even have completely different preprocessors. I
cannot write code for an abstract compiler, so I'm in an embarrasing
situation: either I post unfinished/suboptimal/wrong code, or I don't
post anything, so anyway I'll fall in the disgrace of this honored
newsgroup.
I asked for a public CGI-driven C++ state-of-the-art compiler or at
least a code checker, with way too little echo.
Andrei
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/24 Raw View
Andrei Alexandrescu wrote:
> struct Foo
> {
> char function1(int);
> double function2(int, double);
> };
> Foo Object;
> closure<char, int> MyClosure1(Object, Foo::function1);
> closure<char, int, double> MyClosure2(Object, Foo::function2);
> char c = MyClosure1(5);
> double d = MyClosure2(5, 6.78);
.....
> Whaddaya think? Eh eh?
I think you should reveal the closure<> template that magically makes all
this work. I'd like to know how this template takes any number of template
types and other sundry matters. Let's see its code so we can all examine it
and see if it really works.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1999/02/25 Raw View
Sorry to reply to my own post, but there are a few improvements that
were added to my implementation of closures overnight:
1. Currying. Yes indeed, currying *is* doable! The call to curry(arg)
for a closure returns a closure with one argument less, e.g:
closure<int, char, double> Closure1(Object, &Class::Method);
closure<int, char> Closure2(Closure1.curry(4.5));
// call Object.Method('a', 4.5)
Closure2('a');
But wait, there's more! There is also curry_ref() which allows you to
curry to a reference so you can change the currying parameter after
the facts! That is:
closure<int, char, double> Closure1(Object, &Class::PointerToMethod);
double Parameter = 10.5;
closure<int, char> Closure2(Closure1.curry_ref(Parameter));
// call Object.Method('a', 10.5)
Closure2('a');
// call Object.Method('a', 55.5)
Parameter = 55.5;
Closure2('a');
Now of course you have the lifetime issue (the argument to curry_ref
should outlive any call to the closure) but I think the feature is
powerful enough to introduce it. What do you think?
Cost: one extra virtual call per parameter 'curryed'.
2. Constness. There is a constructor now that takes a pointer to a
constant object and a const method.
3. Allow more parameters without putting a strain on compile time.
I've done some macro stuff which basically allows you to control the
maximum number of parameters for a closure. The point is that if I
defined closures with many args, the header would have become big
enough to slow down compile time a lot. Now you #define
CLOSURE_MAXARGS as an integer to specify the maximum number of args
for the closures in your project. I've set the physical limit to 21 -
three times the number of variables a human being can handle
comfortably. If you don't #define CLOSURE_MAXARGS, it defaults to 4.
4. Construct closures from regular pointers to functions. There is a
constructor now that takes a pointer to function. Advantages: you have
a single model for all objects that you can call into; and you can do
currying.
Cost: one extra virtual call.
5. Construct closures from STL functors. There is a constructor that
takes an object, stores it by value, and upon invocation calls that
object's operator(). Technically this is the same as storing a pointer
to a function and invoking it, so it came for free. (Speaking of STL,
closures and curryed closures act as functors with STL algorithms and
structures as you'd expect.)
Cost: one extra virtual call.
6. Efficiency.
I know of a *portable* technique that would get rid of the extra
virtual call cost.
For compilers satisfying a certain condition, fast code is generated.
For the others, a slower and more general version is generated. The
condition is detected at compile time and the appropriate version is
selected without the user having to lift a finger.
Basically the cost in the happy case would be only an extra regular
call. Quite low for such a great abstraction. Also heap usage becomes
unnecessary (except for case 5 above). I think in this case it's all
just as efficient as Borland closures which are non-portable.
However, the technique is intricate enough to save it for the most
demanding tasks only. I plan to put later together an
industrial-strength version. For now I will stick to the more costly
but simpler implementation.
Please tell me what you think.
Andrei
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1999/02/25 Raw View
Edward Diener wrote in message <36D47623.17D2ACA5@abraxis.com>...
>
>Andrei Alexandrescu wrote:
>
>> struct Foo
>> {
>> char function1(int);
>> double function2(int, double);
>> };
>> Foo Object;
>> closure<char, int> MyClosure1(Object, Foo::function1);
>> closure<char, int, double> MyClosure2(Object, Foo::function2);
>> char c = MyClosure1(5);
>> double d = MyClosure2(5, 6.78);
>I think you should reveal the closure<> template that magically makes all
>this work. I'd like to know how this template takes any number of template
>types and other sundry matters. Let's see its code so we can all examine it
>and see if it really works.
Please don't expect that much. I told you that you cannot have quite
*any* number of template arguments. I have set the physical limit to
21. In the meantime, yes, the exact code above compiles and runs as
expected.
Cheers,
Andrei
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/02/25 Raw View
Andrei Alexandrescu wrote:
[... explanation of obviously great implementation of closures ...]
> It works (it took me great pains to achieve it). If you implicitly can
> convert the result to the target result (in our case int to char), it
> works. Otherwise, it's a compile-time error. I guess you couldn't
> dream for more. The explicit conversion case is also doable I guess,
> but I've been lazy.
> There is a cost, though: one extra virtual call for the conversion.
>
> Now for some final considerations:
> - the implementation doesn't require macro usage.
> - it doesn't even make use of *any* cast at all, only clean (albeit
> mind-boggling :o}) C++ techniques.
> - it's standard, there are no gotchas that I know of, except that it
> puts a strain on your compiler. It has to support partial
> specialization and such intricate stuff. Don't even think of compiling
> it with MSVC 6.
>
> Please tell me whether you find this satisfactory.
I'm not the one asked, but to me it seems quite satisfactory
(although I can imagine to make it even more satisfactory,
see my dreams below ;-)).
Only one unsatisfactory part is in it: It's not in the
standard library :-)
>
> There is also a lot of room for improvement. What about currying? This
> is not implemented yet, but I have serious reasons to think it's
> doable:
>
> struct Foo
> {
> char function1(int);
> double function2(int, double);
> };
> Foo Object;
> closure<char, int, double> MyClosure1(Object, Foo::function2);
> closure<char, int> MyClosure2 = MyClosure1.curry(18.9);
> // invoke Object.function2(1, 18.9)
> char c1 = MyClosure2(1);
> closure<char> MyClosure3 = MyClosure1.curry(18.9).curry(5);
> // invoke Object.function2(5, 18.9) !!!
> char c2 = MyClosure3();
>
> Whaddaya think? Eh eh?
The currying part would be really nice. However, there could
be a way to say _which_ arguments are curried (yes, I know
functional languages don't do this; however, in functional
languages, you can have adaptor functions on the fly). They
might be additional template parameters, defaulted to
1, 2, 3, ... (depending on the number of arguments).
So one could f.ex. do
closure<char, double> MyClosure4 = MyClosure2.curry<1>(7);
closure<char, int> MyClosure5 = MyClosure2.curry<2>(3.5);
closure<char> MyClosure6 = MyClosure.curry<1, 2>(8, 0.5);
BTW, I think you got the usual currying backward. AFAIK,
normally the _first_ arguments get bound on currying.
If selecting is provided, probably the function should be
called bind instead of curry. Currying would just be a special
way of binding (with using the default parameters).
So we would have
c.bind<1>(param) (the same as c.bind(param))
c.bind<2>(param)
c.bind<1,2>(param1, param2) (the same as c.bind(param1, param2) )
Since closures are just function objects, I'd like them to
conform to the function object specification of STL, as far
as appropriate (i.e. unary_function, binary_function), and
in addition give a similar interface for the other pareameters.
Some other nice feature would be the possibility to bind not
only to function pointers, but also to global functions
and function objects (and other closures).
Chaining through bind (i.e. binding to other closures
instead of values) would be nice as well. Also a
"distribute" would be nice, to allow distrributing
one expression to different parameters, and/or reordering.
Examples:
closure<double, double, double, int, bool) c1 = ...;
closure<int, long, bool> c2 = ...;
closure<double, double, int, bool) c3 =
c1.distribute<1,1>();
// c3(a, b, c) calls c1(a, a, b, c);
closure<double, double, long, bool, bool> c4 =
c3.bind<2>(c2);
// c3(a,b,c,d) calls c3(a, c2(b,c), d)
closure<double, int, bool> c4 =
c1.bind<1,2>(3.5, 5.5);
// c4(a, b) calls c1(3.5, 5.5, b, c)
closure<double, int, bool> c5 =
c1.bind(3.5, 5.5);
// same as c4; default order <1, 2, 3, 4>
closure<double, bool, int> c6 =
c5.distribute<2,1>();
// c5(a,b) calls c4(b,a)
This would also allow on-the-fly "functions" which could then
look like this
closure<double, double> f =
closure<double, double, double>(std::multiplies)
.bind(closure<double, double>(std::sqrt),
closure<double, double>(std::sin))
.distribute<1,1>())
This closure would represent the function sqrt(x)*sin(x).
Explanation:
closure<double, double, double>(std::multiplies) makes a closure
from the std::multiplies function object, calculating x*y.
This is bound (on all parameters, in this case) to the
closures encapsulating the math functions std::sqrt and std::sin,
resulting in a closure<double, double, double> calculating
sqrt(x)*sin(y).
For this, a new closure<double, double> is made, which distributes
its only argument to both parameters.
Especially the distribute part is difficult, but I think doable.
Finally, I want the whole thing in the std library ;-)
(And, of course, every compiler supporting the whole standard <g>)
PS: A helper template for distribute could look like this
(only for 3 parameters, but extension should be straightforward;
untested):
template<int n, class T1, class T2, class T2> struct select
{
typedef void type;
};
template<class T1, class T2, class T3> struct select<1, T1, T2, T3>
{
typedef T1 type;
};
template<class T1, class T2, class T3> struct select<2, T1, T2, T3>
{
typedef T2 type;
};
template<class T1, class T2, class T3> struct select<3, T1, T2, T3>
{
typedef T3 type;
};
template<long used, int num, int index1, int index2, int index3>
struct index_select
{
enum { index = index_select<used | (1<<index1),
n-1, index2, index3, 0>::index };
};
template<long used, int index1, int index2, int index3>
struct index_select<used, 1, index1, index2, index3>
{
enum { index = ((1<<index1) & used)==0 ? index1 :
index_select<used, 1, index2, index3, 0>::index };
};
template<long used> struct index_select<used, 1, 0, 0, 0>
{
enum { index=0 };
};
template<int n, class Param1, class Param2, class Param3,
int index1, int index2, int index3>
struct new_param
{
typedef select<index_select<0, n, index1, index2, index3>::index,
Param1, Param2, Param3>::type param;
};
The definition of the distribute member could then look like this:
template<class Ret, class P1, class P2, class P3>
template<int i1, int i2=2, int i3=3>
closure<Ret,
new_param<1, P1, P2, P3, i1, i2, i3>::param,
new_param<2, P1, P2, P3, i1, i2, i3>::param,
new_param<3, P1, P2, P3, i1, i2, i3>::param>
closure<Ret, P1, P2, P3>::distribute()
{
...
};
This assumes void for non-present parameters. The "no parameter"
type is the void in the non-specialized struct select template,
so it's easy to change it.
Maybe one could get a step further and hide nearly the complete
closure mechanism behind expression templates. Then, one could
write the last example as
closure<double, double> f =
(sqrt(param<double>())*sin(param<double>()).distribute<1,1>();
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: James Kuyper <kuyper@wizard.net>
Date: 1999/02/25 Raw View
Andrei Alexandrescu wrote:
>
> Edward Diener wrote in message <36D47623.17D2ACA5@abraxis.com>...
> >
> >Andrei Alexandrescu wrote:
> >
> >> struct Foo
> >> {
> >> char function1(int);
> >> double function2(int, double);
> >> };
> >> Foo Object;
> >> closure<char, int> MyClosure1(Object, Foo::function1);
> >> closure<char, int, double> MyClosure2(Object, Foo::function2);
> >> char c = MyClosure1(5);
> >> double d = MyClosure2(5, 6.78);
...
> 21. In the meantime, yes, the exact code above compiles and runs as
> expected.
Yes, but you've hidden the most important part of it. You haven't shown
the definitions of your closure<> template. There are some people who
are apparantly skeptical about whether such a template is feasible, and
would like to see how it's done. I think it's feasible, and I've a
pretty good idea how it's done, but I'd like to see it too.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/22 Raw View
John_Maddock@compuserve.com (John Maddock) wrote:
> "myObjectPtr" is a pointer to object,
> "myClass::memberFunction" is a pointer to member function (just like a
> regular function pointer), and "myObjectPtr->memberFunction" is
> currently a syntax error, so no existing behaviour is changed.
A slight correction: myObjectPtr->memberFunction is a perfectly
reasonable expression in the current grammar -- there just isn't any
agreed-upon name for its type, either in C++ or English.
(Also, I assume you meant to write "&myClass::memberFunction.")
I'm personally undecided as to whether notion of bound member function
should be described (and represented in C++ syntax) as having anything
to do with pointers. As I see it, there are two choices:
Treat them like objects which have an operator(). With this choice,
bound member functions are generated by these existing expressions:
object.memberFunction object.*pointerToMemberFunction
pointer->memberFunction pointer->*pointerToMemberFunction
As a more ambitious extension, simply "memberFunction" or
"*pointerToMemberFunction" might be allowed within a class context.
But currently "*pointerToMember" is not a valid expression in any context.
The alternative would be to treat them as if they pointed to an object
with operator(). As I see it, this approach nets:
Two analogies: one with pointers to functions, and
one with pointers to members.
A more obvious meaning for zero-initialization.
More punctuation (ampersands in creating them; asterisks and
parentheses in calling them).
An extra type -- there will need to be formal types for both
boundMemberPointer and *boundMemberPointer. (Consider that
the standard currently mentions references to plain functions,
as the type of *functionPtr.)
As you may have guessed, I tend toward the leaner first approach.
I suspect the analogies the second provides may mislead more than
enlighten. But existing practice seems to favor the second approach.
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/23 Raw View
"Andrei Alexandrescu" <alexandrescua@micromodeling.com> asks:
> Could someone put clearly in place a set of requirements (a wish-list)
> for Borland-style closures? Thanks.
Since I seem to be the loudest proponent lately, I'll take a stab.
Before I begin, I'll note that I have no direct knowledge of what
Borland does; this is really just my wish list. And while I describe
a particular syntax here, I allow that actual syntax may vary.
First, the basics. Consider the expression "(object.method)(parameters)."
We want, in effect, to be able to compute the subexpression
"object.method," store it, pass it to other functions, and later,
supplying the parameters, call it. We'll call this sort of value
a closure.
As a reference point, consider "function(parameters)." We can store
&function, pass it around, and call it.
It's important that the type of the closure for "object.method" depend
on the return type, parameter list, and throw-specification of
"method," but not on the type or cv-qualification of "object." The latter
items must figure in to whether forming the closure is legal.
(I think that including the throw-specification here is stronger than
what function pointers do. If you want to follow those rules, fine.)
The calling expression, say "closure(parameters)," must be typesafe in
terms of the parameters and return the appropriate value.
The closure for "object.method" must be callable at any time during
the lifetime of "object."
It must also be possible to create a closure from a pointer-to-method,
as in "object.*pointerToMethod."
Now it gets harder:
Closures, like function pointers, should have ordinary value semantics.
One should be able to pass them to and return them from functions,
and store them in appropriately typed variables.
Constructing or destorying a closure should never throw an exception.
Calling a closure should only throw exceptions in the same situations
calling a function pointer would.
To create a closure from "object.method," one must have access to
"object" and "method." In calling a closure, the access rules
should be the same as when calling a function pointer.
The rules for temporary creation when calling a closure should be the
same as when calling a function pointer.
Closures with any number of parameters should be allowed.
One should be able to create a closure for "object.qualifier::name,"
with the expected results. If a closure for "object.method" is formed
during the construction of "object," it should never refer to a
method of a derived class. (That is, the referrent of a closure
should be fixed, even as the dynamic type of the object varies.)
And, for bonus points, allow implicit conversions which weaken
throw-specifications or are covariant with the return type. Allow
explicit conversions reversing these.
I will buy a tasty dinner in Berkeley for the first person to post
a usable closure mechanism written in standard C++ which meets these
criteria. If you get the bonus points, you get dessert.
--Lisa Lippincott
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/23 Raw View
Andrei Alexandrescu wrote:
> Sorry I'm late in all this discusstion. As it became deeper and deeper
> stuck in details, I'm not sure whether I've understood the basic
> requirements for "Borland-style closures".
>
> If I understood correctly, the need is for a type that holds a pointer
> to an object, together with a pointer to a method of that object. Kind
> of a "bound pointer to member function", right? Once this object's
> "fire" method is invoked, the specified method is invoked for the
> specified object.
Yes, that is essentially it. In Borland's C++ Builder you can assign any
object's function whose prototype matches the "Borland-style closure"'s
protoype to it..
> I know of an elegant C++ technique that allows this in a reusable
> manner. It actually allows exactly what was stated above, without much
> clumsiness (no explicit template parms etc.). However, prior to
> putting the code in place, I just wanted to know what exactly is
> required.
Fire away. I'd like to see it.
> Could someone put clearly in place a set of requirements (a wish-list)
> for Borland-style closures? Thanks.
Quite simply I want a Borland-style closure to be flexible enough to be part
of any function prototype and to which can be assigned a member function of
any object that matches that function prototype. This allows a member
function of any object to "hook" the closure and subsequently be called
through that closure. Borland uses this to implement events in C++ Builder
but I see it also as a natural extension to the C++ language. If C++ can
have, matching a given function prototype, pointers to functions and pointers
to member functions of a particular class, why can it not have pointers to
member functions of any class.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1999/02/23 Raw View
Lisa Lippincott wrote in message ...
[almost impossible to accomplish wish-list for closures in Standard
C++ snipped]
>I will buy a tasty dinner in Berkeley for the first person to post
>a usable closure mechanism written in standard C++ which meets these
>criteria. If you get the bonus points, you get dessert.
>
> --Lisa Lippincott
Now look what's the deal.
I'll do my best in fulfilling those requirements. The outcome won't be
worth a dinner, though.
What about a burger at McDonald's? As of the dessert, I will skip it.
It's fattening.
Hope to be back soon,
Andrei
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1999/02/24 Raw View
Okay Lisa, I just finished the code. Prior to post the gazillion lines
of transmission line noise, please tell me whether it satisfies your
wish list. If it does, you owe me dinner. Please see my insertions
below.
Sorry for the long message, but I hope the stuff is interesting enough
to be worth the effort.
Lisa Lippincott wrote in message ...
>First, the basics. Consider the expression
"(object.method)(parameters)."
>We want, in effect, to be able to compute the subexpression
>"object.method," store it, pass it to other functions, and later,
>supplying the parameters, call it. We'll call this sort of value
>a closure.
>
>As a reference point, consider "function(parameters)." We can store
>&function, pass it around, and call it.
Okay, what about:
struct Foo
{
char function1(int);
double function2(int, double);
};
Foo Object;
closure<char, int> MyClosure1(Object, Foo::function1);
closure<char, int, double> MyClosure2(Object, Foo::function2);
char c = MyClosure1(5);
double d = MyClosure2(5, 6.78);
Please tell me whether this syntax is satisfactory.
Cost of invocation: one extra virtual call.
>It's important that the type of the closure for "object.method"
depend
>on the return type, parameter list, and throw-specification of
>"method," but not on the type or cv-qualification of "object." The
latter
>items must figure in to whether forming the closure is legal.
>(I think that including the throw-specification here is stronger than
>what function pointers do. If you want to follow those rules, fine.)
Oops. Sorry to dissapoint you: throw specification is not part of the
function/method specification. I've been lazy enough not to care about
the constness, but it should be a breeze. Anyway, as you saw above, in
the parameter list there's no mention to the type of the subject of
the closure (Foo).
>The calling expression, say "closure(parameters)," must be typesafe
in
>terms of the parameters and return the appropriate value.
Yes it is. Using the code above, the calls:
// cannot convert a char to a char * in return value
char * Wrong = MyClosure1(5);
// cannot convert char * to int in parm list
char StillWrong = MyClosure1("blah");
are illegal and detected appropriately at compile time.
>The closure for "object.method" must be callable at any time during
>the lifetime of "object."
Sure it is.
>It must also be possible to create a closure from a
pointer-to-method,
>as in "object.*pointerToMethod."
I'm not sure what you mean here. There is a constructor that takes an
object and a pointer to a method belonging to that object. That's it!
Is this okay to you?
>Now it gets harder:
Not much.
>Closures, like function pointers, should have ordinary value
semantics.
>One should be able to pass them to and return them from functions,
>and store them in appropriately typed variables.
No sweat. Using the code above, let's add another class and change
MyClosure1:
struct Boo // unrelated to Foo
{
char function1(int);
double function2(int, double);
};
Boo MyBoo;
MyClosure1 = closure<char, int>(MyBoo, Boo::function1);
You can copy closures, store closures, assign them to each other, put
them on the heap, make them static, whatever you want for God's sake.
They are first-class types all right.
>Constructing or destorying a closure should never throw an exception.
Hmmm... there's a call to 'new' in the constructor. This is because I
don't assume all pointers to all member functions are the same size.
If they were, there wouldn't be any calls to 'new' so your requirement
would be fulfilled. But anyway, my 'new' allocates few memory, so if
it fails, your program really is in big trouble. So I think I fulfill
this requirement.
>Calling a closure should only throw exceptions in the same situations
>calling a function pointer would.
Of course.
>To create a closure from "object.method," one must have access to
>"object" and "method." In calling a closure, the access rules
>should be the same as when calling a function pointer.
Yep.
>The rules for temporary creation when calling a closure should be the
>same as when calling a function pointer.
Ditto.
>Closures with any number of parameters should be allowed.
Here I have to exercise my negociator skills. C'mon, Lisa, be
reasonable, you know one cannot do this kind of magic in C++. I've
given this a LOT of thought over time. "How can we overcome the fact
that C++ doesn't have a decent variable argument mechanism, nor
variable arguments templates and even lacks a variable-argument
preprocessor"? I reached a surprising conclusion.
Do you really, really want *infinite* arguments for this closure? No?
How many then?
The point is, any coding standard puts a limit on number of arguments.
Many say that a function with over 7 args is a sign of bad design.
(Over this limit, one should pack args in a structure.)
I *had* to put a limit. You name it, but it's limited. Say 10
arguments? You have to live with it. If you reach that limit, your
design is not quite award-winning anyway.
>One should be able to create a closure for "object.qualifier::name,"
>with the expected results. If a closure for "object.method" is
formed
>during the construction of "object," it should never refer to a
>method of a derived class. (That is, the referrent of a closure
>should be fixed, even as the dynamic type of the object varies.)
I'm not sure I understand here. Closures should follow the virtual
mechanism, right? So what's your point?
>And, for bonus points, allow implicit conversions which weaken
>throw-specifications or are covariant with the return type. Allow
>explicit conversions reversing these.
Guess what, I've done it. The following works fine:
struct S1
{
int fun();
};
struct S2 // related to S1 or not
{
char fun();
};
S1 s1;
S2 s2;
closure<int> MyClosure(s1, S1::fun);
MyClosure = closure<char>(s2, S2::fun); // !!!
It works (it took me great pains to achieve it). If you implicitly can
convert the result to the target result (in our case int to char), it
works. Otherwise, it's a compile-time error. I guess you couldn't
dream for more. The explicit conversion case is also doable I guess,
but I've been lazy.
There is a cost, though: one extra virtual call for the conversion.
Now for some final considerations:
- the implementation doesn't require macro usage.
- it doesn't even make use of *any* cast at all, only clean (albeit
mind-boggling :o}) C++ techniques.
- it's standard, there are no gotchas that I know of, except that it
puts a strain on your compiler. It has to support partial
specialization and such intricate stuff. Don't even think of compiling
it with MSVC 6.
Please tell me whether you find this satisfactory.
There is also a lot of room for improvement. What about currying? This
is not implemented yet, but I have serious reasons to think it's
doable:
struct Foo
{
char function1(int);
double function2(int, double);
};
Foo Object;
closure<char, int, double> MyClosure1(Object, Foo::function2);
closure<char, int> MyClosure2 = MyClosure1.curry(18.9);
// invoke Object.function2(1, 18.9)
char c1 = MyClosure2(1);
closure<char> MyClosure3 = MyClosure1.curry(18.9).curry(5);
// invoke Object.function2(5, 18.9) !!!
char c2 = MyClosure3();
Whaddaya think? Eh eh?
Andrei
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/24 Raw View
John Maddock wrote:
> >I would prefer to say that "myObjectPtr->memberFunction;" could be cast to a "closure"
> >since at present such an expression returns a function pointer to a specific class.
> >But if the "closure" concept as an "unbounded function pointer", ala C++ Builder, were
> >implemented in C++ then indeed an automatic cast to a "closure" of the expression
> >above could be made available.
>
> Not quite "myObjectPtr" is a pointer to object,
> "myClass::memberFunction" is a pointer to member function (just like a
> regular function pointer), and "myObjectPtr->memberFunction" is
> currently a syntax error, so no existing behaviour is changed.
You are right. An expression such as "myObjectPtr->memberFunction" is essentailly a
"closure" in C++ Builder.
> >Yes, that would be very useful indeed but it seems the standard library would have to
> >be extended to support it. However the usefulness of the concept is just one more
> >reason for supporting the "unbounded function pointer" concept. Thanks for pointing it
> >out.
>
> No the library would not need to extended, Borland style closures
> behave like regular function pointers, and are typed and called just
> like regular function pointers; so just as you can use a function
> pointer as a function object in the standard library, so you can a
> closure.
I will rephrase. The standard library would have to be changed to accept Borland style
closures as function pointers or as function objects. I also did not think that Borland
style closures in C++ Builder could be used in place of function pointers or function
objects, considering that they are a different type than either of the others. I believe a
Borland style closure holds both the "this" pointer as well as the member function pointer,
8 bytes, while a regular function pointer is 4 bytes. Of course if you can use a Borland
style closure as a normal function pointer in C++ Builder, it shouldn't be that hard to
implement the same thing in a future version of C++. As you have pointed out so well, it
would be a great way to make C++ an even better language and would find immediate use in
the STL.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/02/24 Raw View
>A slight correction: myObjectPtr->memberFunction is a perfectly
>reasonable expression in the current grammar -- there just isn't any
>agreed-upon name for its type, either in C++ or English.
Possibly, but its still a syntax error unless followed by operator().
>(Also, I assume you meant to write "&myClass::memberFunction.")
No, you don't need the & to take an address of a static non-member
function, any more than you need the * to call it via a function
pointer, the same would be true of "closures".
What I'm saying is that a bound pointer to member function (aka
closure) should be treated in exactly the same way as a pointer to a
global static function.
John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: pixi@bloodbath.burble.org (maurice s. barnum)
Date: 1999/02/24 Raw View
"Andrei Alexandrescu" <alexandrescua@micromodeling.com> writes:
: Sorry I'm late in all this discusstion. As it became deeper and deeper
: stuck in details, I'm not sure whether I've understood the basic
: requirements for "Borland-style closures".
:
: If I understood correctly, the need is for a type that holds a pointer
: to an object, together with a pointer to a method of that object. Kind
: of a "bound pointer to member function", right? Once this object's
: "fire" method is invoked, the specified method is invoked for the
: specified object.
an additonal requirement is that a "bound pointer to member function"
is assignment compatible with any other "bound pointer to member
function", ignoring the type of the object being bound. the fact that
both the object and method are packed up in a single addressible
object isn't very interesting, since a struct would do the job just
fine.
struct foo {
void on_exit();
void on_abort();
};
struct bar {
void on_exit();
};
typedef void (__closure *exit_event)();
void func()
{
foo f;
bar b;
exit_event e1;
exit_event e2;
// a few legal assignments
e1 = f->on_exit;
e2 = e1;
e1 = b->on_exit;
e2 = f->on_abort;
// now fire the events.
e1();
e2();
}
the same effect can be achived in C++ in a relatively straightforward
manner with the proverbial extra level of indirection, a bit more
typing, and a hit on runtime efficiency.
--xmsb
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/18 Raw View
Dave Abrahams <abrahams@mediaone.net> writes:
> class Button : public virtual Broadcaster
> {
> public:
> class Listener
> {
> virtual void Pressed( Button* ) = 0;
> }
>
> template <class T>
> class Press : public virtual Receiver, public Button::Listener
> {
> public:
> void Press( Button* b, T* o, void (T::*x)() )
> : obj(o), pmf(x) { Subscribe(b); }
> private:
> void Pressed( Button* ) { obj->pmf(); }
> T* obj;
> void (T::*pmf)();
> };
> };
And shows that he's beginning to see the light. Apart from inherinting
from Receiver, Button::Press is the usual C++ implementation of a
closure on the this parameter. Compare it, for example, to the code
Jean-Louis Leroy recently posted.
And on performance:
> But this kind of performance concern is typically misplaced in UI
> applications, where the cost of checking against a linear list of addresses
> is usually overwhelmed by the cost of updating the display and masked by
> slow user reaction times.
I used a user interface example because I thought it would be a
particularly familiar form of event-driven programming. Towards the
other end of the performance spectrum, I also use closures heavily
when I work with the Macintosh's low level I/O system, which is based
on a continuation mechanism.
> I just don't see it as compelling enough to warrant a new language feature.
That's hardly surprising, since you don't seem inclined to use them
in the first place. And of course, even among those who would use them,
opinions will vary on the burden of working around their absence.
As for me: I can get by, but I'm peeved by both the linguistic
awkwardness and the fact that the workarounds pessimize code that
should be optimized by the introduction of closures.
As long as I'm enumerating viewpoints, I'll add this one that I've
recently come across. It's a note from the ARM, section 5.5, although
I think it applies equally well to section 5.2.4:
Naturally, it would be possible to generalize the notion
of a pointer to member to allow bound pointers, such as
ptr_to_object->*ptr_to_mfct, to be stored and, in general,
be treated as first class objects. Doing so, however, would
open vast opportunities for generalization and language
extension in the general area of "what is a function and how
can I call it?" and would require implementation techniques
outside the realm of traditional C techniques. It was felt
that restraint was in order.
While it was very hard to argue with a call for restraint in 1990,
I think the 1988 standard gives us more room for discussion. As for
the objections given, I think the implementation techniques can be
(and largely have been) easily addressed, and "What is a function?"
was already made moot by 1990 with the introduction of overloaded
operator().
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/02/18 Raw View
Lisa Lippincott wrote:
[...]
> > I just don't see it as compelling enough to warrant a new language feature.
>
> That's hardly surprising, since you don't seem inclined to use them
> in the first place. And of course, even among those who would use them,
> opinions will vary on the burden of working around their absence.
I also don't think it should be a language feature.
However, it should be a standard library feature.
This is, because it is generally useful, and while it
can be implemented in standard conforming C++, it could
be vastly improved with compiler help (f.ex. by implementing
is as a simple inline wrapper over existing non-standard
closures).
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/19 Raw View
Christopher Eltschka wrote:
> I also don't think it should be a language feature.
>
> However, it should be a standard library feature.
> This is, because it is generally useful, and while it
> can be implemented in standard conforming C++, it could
> be vastly improved with compiler help (f.ex. by implementing
> is as a simple inline wrapper over existing non-standard
> closures).
It's easy to say that it should be implemented as part of the standard library but
until someone shows this "implementation" to me as a generic solution of the idea
of a closure, I would prefer it to be part of the language. Of course I am talking
about a closure as implemented by Borland in C++ Builder, which is essentially a
keyword that can be added to a pointer to a function specification that allows that
pointer to point to any appropriate member function in any class, while you may be
talking about another idea entirely. I see no reason why the C++ Builder idea of
closure should not be added to the language and many reasons why it would solve the
"event handling" paradigm which has been discussed in this thread and for which
elaborate template implementations have been offered as the solution. Perhaps the
"language feature" addition would be the easiest and most elegant solution and
would justify itself on that basis.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/19 Raw View
Lisa Lippincott wrote:
> As for me: I can get by, but I'm peeved by both the linguistic
> awkwardness and the fact that the workarounds pessimize code that
> should be optimized by the introduction of closures.
Thanks, Lisa. I just wanted to add here that while I am an admirer of the power
of templates, in the case of emulating closures, it just seems like too much work
to do it for every situation. A simple addition to the language of the closure
concept would be an infinitely easier way of creating the ability to hook to
"events". I also have still not seen a single generic solution to the whole
closure idea as implemented by templates while a closure as part of the language
would represent that simple solution that could be applied to all cases.
> As long as I'm enumerating viewpoints, I'll add this one that I've
> recently come across. It's a note from the ARM, section 5.5, although
> I think it applies equally well to section 5.2.4:
>
> Naturally, it would be possible to generalize the notion
> of a pointer to member to allow bound pointers, such as
> ptr_to_object->*ptr_to_mfct, to be stored and, in general,
> be treated as first class objects. Doing so, however, would
> open vast opportunities for generalization and language
> extension in the general area of "what is a function and how
> can I call it?" and would require implementation techniques
> outside the realm of traditional C techniques. It was felt
> that restraint was in order.
Wonderful ! When I propose closures, similar to what C++ Builder implements, for
the next version of C++ I will quote this passage. Obviously those
"implementation techniques" can be done by compiler vendors today or Borland
would not have done it so smoothly.
> While it was very hard to argue with a call for restraint in 1990,
> I think the 1988 standard gives us more room for discussion. As for
> the objections given, I think the implementation techniques can be
> (and largely have been) easily addressed, and "What is a function?"
> was already made moot by 1990 with the introduction of overloaded
> operator().
I totally agree. Even if some people find no need for closures, it does not mean
it has no value for others. Since pointers of a given type to class member
functions became acceptable as an idea in the C++ world and usable in real world
applications, there is no reason why a generalized pointer of a given type to any
class's member function can not become acceptable as an idea in the C++ world,
while its use in real world applications can be easily illustrated by C++
Builder's use of this idea to create run time event handling. The fact that C++
Builder also extends this to design time event handling through its use of its
"__property" extension to C++ and Windows resource files is really a moot point.
Even hooking to events at runtime through closures is an enormous improvement in
simplification than any templated approach.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Ross Smith" <ross.s@ihug.co.nz>
Date: 1999/02/20 Raw View
Edward Diener wrote in message <36CCAB98.4E69A9AB@abraxis.com>...
>
>Thanks, Lisa. I just wanted to add here that while I am an admirer of the power
>of templates, in the case of emulating closures, it just seems like too much work
>to do it for every situation. A simple addition to the language of the closure
>concept would be an infinitely easier way of creating the ability to hook to
>"events". I also have still not seen a single generic solution to the whole
>closure idea as implemented by templates while a closure as part of the language
>would represent that simple solution that could be applied to all cases.
I'd just like to make a point that has been mentioned before in this
thread but probably needs to be reiterated: The concept you're
proposing may be a good idea, but I'm certain you'd get a more
sympathetic reaction from the experts if you for heaven's sake *stop
calling them closures*!
The term "closure" has a long-standing, reasonably well-defined meaning
in computer science, and the things under discussion here bear very
little resemblance to it. Just because Borland used that name is no
reason to compound the error.
Perhaps something like "unbound [or "generic"?] member pointers"?
--
Ross Smith ................................... mailto:ross.s@ihug.co.nz
.............. The Internet Group, Auckland, New Zealand ..............
"The award for the Most Effective Promotion of Linux
goes to Microsoft." -- Nicholas Petreley
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Dave Abrahams" <abrahams@mediaone.net>
Date: 1999/02/20 Raw View
In article <lisa_lippincott-1702991710210001@38.168.253.3> ,
lisa_lippincott@advisories.com (Lisa Lippincott) wrote:
>> But this kind of performance concern is typically misplaced in UI
>> applications, where the cost of checking against a linear list of addresses
>> is usually overwhelmed by the cost of updating the display and masked by
>> slow user reaction times.
>
> I used a user interface example because I thought it would be a
> particularly familiar form of event-driven programming. Towards the
> other end of the performance spectrum, I also use closures heavily
> when I work with the Macintosh's low level I/O system, which is based
> on a continuation mechanism.
If you're worried about the cost of a couple of function calls in your I/O
completion callbacks, you've either got the fastest I/O devices on the
planet or your concern is once again misplaced.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/02/20 Raw View
>Thanks, Lisa. I just wanted to add here that while I am an admirer of the power
>of templates, in the case of emulating closures, it just seems like too much work
>to do it for every situation. A simple addition to the language of the closure
>concept would be an infinitely easier way of creating the ability to hook to
>"events". I also have still not seen a single generic solution to the whole
>closure idea as implemented by templates while a closure as part of the language
>would represent that simple solution that could be applied to all cases.
>
Even if a general template case were available, it still does not
address one key point: that statements of the type:
myObjectPtr->memberFunction;
Automatically return a type "closure".
That means that closures can be used as function objects for standard
library functions in the same way that regular function pointers can,
any that's very very useful indeed....
John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/20 Raw View
When this thread was originally started it was to discuss the inclusion of
"properties" and "closures" as implemented by Borland as an addition to C++. As the
discussion continued, many programmers began discussing "closures" as they understand
it based on other languages and provided various templatized solutions to their idea
of "closures". I agree with you that the term that Borland uses may not be the term
that is understood in computer science, and I would welcome a good definition of that
term, but it is not I who have drawn the discusion away from the original idea that
was first started in this thread, but others. I do not have any other term than
"closure" to discuss Borland's "closure" implementation but your substitution of
"unbound [or "generic"?] member pointers" is fine with me. In Borland's C++ Builder it
is called "__closure", so I have naturally used that terminology.
Whatever the terminology is, I think the general discussion on "closures" has mostly
stayed on a certain track. Which is, is there a elegant way in C++ to provide a
methodology by which an "event", taken in its broadest sense, can be created by an
object and responded to by another object. I think the "Borland generic member
pointer" is an excellent beginning solution to this problem in C++ and would welcome
it is an extension to the C++ language even if it did not solve this specific problem
since it seems a natural extension to the "pointer to function" concept in C++.
> I'd just like to make a point that has been mentioned before in this
> thread but probably needs to be reiterated: The concept you're
> proposing may be a good idea, but I'm certain you'd get a more
> sympathetic reaction from the experts if you for heaven's sake *stop
> calling them closures*!
>
> The term "closure" has a long-standing, reasonably well-defined meaning
> in computer science, and the things under discussion here bear very
> little resemblance to it. Just because Borland used that name is no
> reason to compound the error.
>
> Perhaps something like "unbound [or "generic"?] member pointers"?
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/21 Raw View
John Maddock wrote:
> Even if a general template case were available, it still does not
> address one key point: that statements of the type:
>
> myObjectPtr->memberFunction;
>
> Automatically return a type "closure".
I would prefer to say that "myObjectPtr->memberFunction;" could be cast to a "closure"
since at present such an expression returns a function pointer to a specific class.
But if the "closure" concept as an "unbounded function pointer", ala C++ Builder, were
implemented in C++ then indeed an automatic cast to a "closure" of the expression
above could be made available.
> That means that closures can be used as function objects for standard
> library functions in the same way that regular function pointers can,
> any that's very very useful indeed....
Yes, that would be very useful indeed but it seems the standard library would have to
be extended to support it. However the usefulness of the concept is just one more
reason for supporting the "unbounded function pointer" concept. Thanks for pointing it
out.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Scott Johnson <sj_nospam@nospam.aracnet.com>
Date: 1999/02/21 Raw View
Edward Diener wrote:
>
> When this thread was originally started it was to discuss the inclusion of
> "properties" and "closures" as implemented by Borland as an addition to C++. As the
> discussion continued, many programmers began discussing "closures" as they understand
> it based on other languages and provided various templatized solutions to their idea
> of "closures". I agree with you that the term that Borland uses may not be the term
> that is understood in computer science, and I would welcome a good definition of that
> term, but it is not I who have drawn the discusion away from the original idea that
> was first started in this thread, but others. I do not have any other term than
> "closure" to discuss Borland's "closure" implementation but your substitution of
> "unbound [or "generic"?] member pointers" is fine with me. In Borland's C++ Builder it
> is called "__closure", so I have naturally used that terminology.
A term Sun used in a white paper they wrote (on the topic of adding
this feature to Java, mainly in response to the MS version of Java
including this feature) was "bound method reference".
Which is a very accurate description of what a C++ Builder "closure" is.
> Whatever the terminology is, I think the general discussion on
> "closures" has mostly stayed on a certain track. Which is, is there a > elegant way in C++ to provide a methodology by which an "event",
> taken in its broadest sense, can be created by an
> object and responded to by another object. I think the "Borland
> generic member pointer" is an excellent beginning solution to this
> problem in C++ and would welcome it is an extension to the C++
> language even if it did not solve this specific problem since it seems > a natural extension to the "pointer to function" concept in C++.
Interface classes work well enough; a closure can be implemented as
a subclass of a base interface class with only one method--operator ().
But in many circumstances, named interfaces (like Java uses) do the
same thing, in a more useful (IMHO) fashion.
Scott
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/22 Raw View
Scott Johnson wrote:
> A term Sun used in a white paper they wrote (on the topic of adding
> this feature to Java, mainly in response to the MS version of Java
> including this feature) was "bound method reference".
That sounds almost right but a reference in C++ is different from a reference in Java and Borland's "closure" is really a version of "pointer"
to class method.
> > Whatever the terminology is, I think the general discussion on
> > "closures" has mostly stayed on a certain track. Which is, is there a > elegant way in C++ to provide a methodology by which an "event",
> > taken in its broadest sense, can be created by an
> > object and responded to by another object. I think the "Borland
> > generic member pointer" is an excellent beginning solution to this
> > problem in C++ and would welcome it is an extension to the C++
> > language even if it did not solve this specific problem since it seems > a natural extension to the "pointer to function" concept in C++.
>
> Interface classes work well enough; a closure can be implemented as
> a subclass of a base interface class with only one method--operator ().
>
> But in many circumstances, named interfaces (like Java uses) do the
> same thing, in a more useful (IMHO) fashion.
Again I would like to see an implementation rather than theory. The strength of Borland "closures" is that it is incredibly easy and elegant to
create and use without the need for intermediate classes, subclasses, templates, hierarchies, or anything else. The problem with your solution,
as with all other solutions, is that your emulation of the Borland "closure" concept must be done for each implementation of "closure" by a
particular class. A solution of Borland "closures" as part of the language wouldn't need this rigamarole. If you have a generic solution for
the Borland "closure" concept, by all means publish it here as actual code for others to look at.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: John_Maddock@compuserve.com (John Maddock)
Date: 1999/02/22 Raw View
>I would prefer to say that "myObjectPtr->memberFunction;" could be cast to a "closure"
>since at present such an expression returns a function pointer to a specific class.
>But if the "closure" concept as an "unbounded function pointer", ala C++ Builder, were
>implemented in C++ then indeed an automatic cast to a "closure" of the expression
>above could be made available.
Not quite "myObjectPtr" is a pointer to object,
"myClass::memberFunction" is a pointer to member function (just like a
regular function pointer), and "myObjectPtr->memberFunction" is
currently a syntax error, so no existing behaviour is changed.
>Yes, that would be very useful indeed but it seems the standard library would have to
>be extended to support it. However the usefulness of the concept is just one more
>reason for supporting the "unbounded function pointer" concept. Thanks for pointing it
>out.
No the library would not need to extended, Borland style closures
behave like regular function pointers, and are typed and called just
like regular function pointers; so just as you can use a function
pointer as a function object in the standard library, so you can a
closure.
John Maddock
http://ourworld.compuserve.com/homepages/John_Maddock/
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1999/02/22 Raw View
Edward Diener wrote in message <36D0D8EE.C601096A@abraxis.com>...
[snip]
>Again I would like to see an implementation rather than theory. The
strength of Borland "closures" is that it is incredibly easy and
elegant to
>create and use without the need for intermediate classes, subclasses,
templates, hierarchies, or anything else. The problem with your
solution,
>as with all other solutions, is that your emulation of the Borland
"closure" concept must be done for each implementation of "closure" by
a
>particular class. A solution of Borland "closures" as part of the
language wouldn't need this rigamarole. If you have a generic solution
for
>the Borland "closure" concept, by all means publish it here as actual
code for others to look at.
Sorry I'm late in all this discusstion. As it became deeper and deeper
stuck in details, I'm not sure whether I've understood the basic
requirements for "Borland-style closures".
If I understood correctly, the need is for a type that holds a pointer
to an object, together with a pointer to a method of that object. Kind
of a "bound pointer to member function", right? Once this object's
"fire" method is invoked, the specified method is invoked for the
specified object.
I know of an elegant C++ technique that allows this in a reusable
manner. It actually allows exactly what was stated above, without much
clumsiness (no explicit template parms etc.). However, prior to
putting the code in place, I just wanted to know what exactly is
required.
Could someone put clearly in place a set of requirements (a wish-list)
for Borland-style closures? Thanks.
Andrei
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/17 Raw View
In article <36C839AC.21665323@physik.tu-muenchen.de>, Christopher Eltschka <celtschk@physik.tu-muenchen.de> wrote:
I wrote:
> Consider the following:
>
> class NoCopy
> {
> private: NoCopy( const NoCopy& );
> public: NoCopy( int );
> };
>
> class RestrictedCopy
> {
> friend void Foo( RestrictedCopy (*)(NoCopy) );
> private: RestrictedCopy( const RestrictedCopy& );
> };
>
> void Foo( RestrictedCopy (*f)(NoCopy) )
> {
> RestrictedCopy r( (*f)( NoCopy( 1 ) ) );
> }
>
> The code above has no access errors.
Christopher Eltschka <celtschk@physik.tu-muenchen.de> replied:
> It has. You create a temporary NoCopy, and pass it per value to
> a function. Pass per value involves a call to the copy constructor.
And he's right; it appears I was overreaching in my example, and
confused direct-initialization with copy-initialization. Here's a
corrected version:
class RestrictedCopy
{
friend void Foo( RestrictedCopy (*)(RestrictedCopy) );
private:
RestrictedCopy( int );
RestrictedCopy( const RestrictedCopy& );
};
void Foo( RestrictedCopy (*f)(RestrictedCopy) )
{
RestrictedCopy r( (*f)( RestrictedCopy(1) ) );
}
It still address the my original point -- function pointers allow this
code, but, to my knowledge, no implementation of closures in C++ can.
Christopher Eltschka goes on to suggest a mechanism for templating on
an entire argument list; I agree that such a capability would be nice,
but I haven't thought deeply about its consequences.
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Dave Abrahams" <abrahams@mediaone.net>
Date: 1999/02/17 Raw View
In article <lisa_lippincott-1502991846130001@38.168.253.3> ,
lisa_lippincott@advisories.com (Lisa Lippincott) wrote:
> My problem with this approach lies in BrowserMediator::Pressed. By
> funelling all button presses through this single method, you create
> a maintainance hassle: this method has to be edited whenever the set
> of buttons changes.
OK:
class Button : public virtual Broadcaster
{
public:
class Listener
{
virtual void Pressed( Button* ) = 0;
}
template <class T>
class Press : public virtual Receiver, public Button::Listener
{
public:
void Press( Button* b, T* o, void (T::*x)() )
: obj(o), pmf(x) { Subscribe(b); }
private:
void Pressed( Button* ) { obj->pmf(); }
T* obj;
void (T::*pmf)();
};
};
class BrowserMediator
{
public:
BrowserMediator()
: back( backButton, this, BrowserMediator::GoBack ),
forward( forwardButton, this, BrowserMediator::GoForward ),
... {}
private:
Button::Press<BrowserMediator> back;
Button::Press<BrowserMediator> forward;
void GoBack();
void GoForward();
}
> You also take a performance hit: you've turned
> what could be an O(1) process into O(n), where n is the number of
> buttons. At the cost of a map, you can get it down to O(log n),
> but going below that gets pretty ugly.
nah, just use a hash table.
But this kind of performance concern is typically misplaced in UI
applications, where the cost of checking against a linear list of addresses
is usually overwhelmed by the cost of updating the display and masked by
slow user reaction times.
> To me, using an if-else if-else construction like this to avoid
> needing closures feels a lot like using a case statement to avoid
> needing virtual methods.
Anyway, my solution above allows you to get your dispatching to go to a
separate member function for each Button, if you really care about that.
> (I also don't see how your Broadcaster/Receiver can work without some
> unpleasant casts, but I think some templatry could get around that.)
It depends if you consider dynamic_cast to be unpleasant. I don't, at least
not in this sort of application.
> Compare the code above to what I would do using closures...
>
> I'm quite fond of using closures this way to hook up an interface.
OK, I compared it. It looks nice, but not particularly nicer than what I
would do without closures. to my eye. I just don't see it as compelling
enough to warrant a new language feature.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/02/15 Raw View
Dave Abrahams wrote:
>
> Lisa Lippincott wrote in message ...
> >"Dave Abrahams" <abrahams@motu.com> replied:
> >
> >> This problem is much better solved by the classic "model-view-controller"
> >> pattern (see smalltalk).
> >
> >In C++, this pattern doesn't break the dependency; it just moves it.
>
> I guess I meant to refer to the mediator pattern instead, but you've surely
> thought of things like this. A mediator is able to completely decouple the
> button and the browser page, so what's the problem?
>
> >If
> >there's a back button, the back function must be implemented somewhere.
> >It could be
> > a) folded into some larger function,
> > b) written as a nonmember function taking one parameter, or
> > c) written as a member function of some class X taking no parameters.
> >
> >I think we agree that (c) is preferable; using a model-view-controller
> >pattern just changes the class denoted by X.
>
> >
> >In C++, without using some closure mechanism, the button code depends
> >both the name of the method and the type of (a base class of) the
> >class X where the implementation (or a forwarder) lies.
>
> I don't see that as a dependency. In my world you'd do something like this:
>
> class Button : public virtual Broadcaster
> {
> public:
> class Listener
> {
> virtual void Pressed( Button* ) = 0;
> }
> };
>
> class WebPage
> {
> void GoToNthHistoryStep( int );
> };
>
> class BrowserMediator : public virtual Receiver, private Button::Listener
> {
> void Pressed( Button* b )
> {
> if (b == back)
> page->GoToNthHistoryStep( 1 );
> ... handle some other buttons ...
> }
> }
>
> You could say that the button implementation "depends on both the name of
> the method and the type of Button::Listener", but since Button::Listener is
> really part of the definition of Button it's not much of a dependency.
However, your listener must explicitly check which button was
pressed - it must therefore know exactly which buttons exist
(what if a back button may or may not exists at different times?)
The closure would free the mediator from this knowledge:
It would still have to know that there may be a no-parameter
event which means to go bach one step of the history, however
it doesn't have to know if that component exists, what that
component is, if there is more than one component which can
trigger a "back" (f.ex. in netscape there's a Back button,
a Back global menu entry and a back local menu entry).
That is, the mediator would be independant from the button
as well, and would become just an extension to the browser.
Indeed, it could be independant of the browser as well, except
that it assumes there exists _some_ go_history function taking
an int)
If the browser itself has a back(), the mediator would not
be needed at all for this, and this would still be transparent
for the button:
class Button
{
public:
closure<void, void> pressed;
Button(closure<void, void> p): pressed(p) {}
...
};
class BrowserWithBack
{
public:
void back();
void forward();
};
class BrowserWithoutBack
{
public:
void go_hist(int n);
};
class Extension
{
public:
Extension(closure<void, int> go, int dist):
go_history(go),
distance(dist)
{
}
go() { go_history(-1); }
private:
closure<void, int> go_history;
int distance;
};
BrowserWithBack b1;
BroserWithoutBack b2;
Extension Back(closure<void, int>(b2, &BrowserWithoutBack::go_history),
-1);
Extension Forward(closure<void, int>(b2,
&BrowserWithoutBack::go_history), 1);
Button back_main(closure<void, void>(b1, &BrowserWithBack::back));
Button back_2(closure(Back, Extension::go));
Button forward_2(closure(Forward, Extension::go));
Menu mainMenu;
mainMenu
.popup("Go")
.item("Back", closure<void, void>(b1, &BrowserWithBack::back))
// try this with your mediator!
.item("Forward", closure<void, void>(b1, &BrowserWithBack::forward))
.endpopup();
Menu Browser2Menu;
Browser2Menu
.popup("Go")
.item("Back", closure<void, void>(Back, &Extension::go))
.item("Forward", closure<void, void>(Forward, &Extension::go))
.endpopup();
You certainly can design classes which do that. But doing so,
you are already doing most of a library implementation of closures.
So why not do the rest as well?
[...]
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/02/15 Raw View
Lisa Lippincott wrote:
>
> I wrote, regarding a closure-on-this mechanism which Jean-Louis Leroy
> <jll@skynet.be> gave in an earlier article:
> > b) It requires a public copy constructor for the return type.
>
> Jean-Louis Leroy asks:
> > You mean the type returned by an invocation of the bound pointer? How is
> > that a problem? Same goes with calling an ordinary function through an
> > ordinary pointer...
>
> Consider the following:
>
> class NoCopy
> {
> private: NoCopy( const NoCopy& );
> public: NoCopy( int );
> };
>
> class RestrictedCopy
> {
> friend void Foo( RestrictedCopy (*)(NoCopy) );
> private: RestrictedCopy( const RestrictedCopy& );
> };
>
> void Foo( RestrictedCopy (*f)(NoCopy) )
> {
> RestrictedCopy r( (*f)( NoCopy( 1 ) ) );
> }
>
> The code above has no access errors.
It has. You create a temporary NoCopy, and pass it per value to
a function. Pass per value involves a call to the copy constructor.
Note that changing f to take a const reference doesn't change this,
since IIRC binding an rvalue to a const reference may involve
making a copy, and therefore the copy constructor must be
available. However, the following code would be legal:
// NoCopy and RestrictedCopy as above, except for changing
// the friend declaration accordingly
void foo(RestrictedCopy(*)(NoCopy const&))
{
NoCopy nc(1); // An lvalue
RestrictedCopy r(f(nc));
}
> If you replace the function
> pointer with a closure, it should still have no access errors.
> But, to my knowledge, every closure-on-this mechanism written in C++
> will introduce extra copies of the parameters and return value
> in other scopes, leading to access errors.
A good point. For parameters, there should be a way to make passing
per const reference possible. However, for return values, you
cannot avoid copies.
But this is no special problem to closures, but it's a problem
for _every_ forwarding function. Therefore, it should get a
general solution (from which a closure implementation could
profit).
A new type specifier "forward", only applied to return types,
would probably be enough. Forward return types can only be
initialized with true rvalues (no lvalue-to-rvalue, to avoid
functions like
X forward foo() { X x; X y; if(bar()) return x; return y; }
which cannot work as intended), and initializing them does
not need a copy constructor.
Basically it would be a way to force the unnamed return value
optimisation, together with forbidding all that would prevent
it, and removing the need to have a copy constructor (since we
know it's not used).
>
> Regarding the limitation on the number of parameters,
> Jean-Louis Leroy writes:
> > That was just an example. A complete implementation would contain
> > bound_pointerXXX for at least 5 args, with a means of generating them for
> > any number of parameters.
>
> I'd be a little unhappy if the C++ standard said that function pointers
> could be used for up to five arguments. And if, as some have suggested,
> a closure mechanism were added to the standard library, I think your
> "means of generating them" would run into problems with the one
> definition rule.
Indeed, here's another point which I would like to have solved far
more general: Forwarding arbitrary many parameters.
This would be possible with an extension to templates, like
template<typename T, typelist L> void foo(T, L);
foo(5); // same as foo<int, ()>(5);
foo(3, "Hello world"); // same as foo<int, (char*)>(5, "Hello world")
foo('a', 'b', 'c'); // same as foo<char, (char, char)>('a', 'b', 'c')
template<typelist P> void bar(T t, L l)
{
g(5, l);
}
void f()
{
bar(); // bar<()>, results in g(5)
bar(1); // bar<(int)>, results in g(5, 1)
bar('x', "x"); // bar<(char, char*)>, results in g(5, 'x', "x");
}
>
> To some, these may be minor problems with a library implementation.
> But I think they begin to answer the "you can implement it just as
> well in a library" argument.
True, but this is more an argument to solve the general
implementation problems, which would be of more general use.
For example, functions like the following would profit as well:
template<typename Ret, typelist Args>
Ret forward locked(Ret (*f)(Args), Args a)
{
Lock lock; // constructor locks, destructor unlocks
return f(a);
}
class Y { friend void f(); Y(Y const&); };
class Z { friend void f(); Z(Z const&); };
class X { friend void f(); friend X foo(Y, Z); X(X const&); };
X foo(Y, Z);
void f()
{
Y y;
Z z;
X x=locked(foo, y, z);
}
While in this simple example, the function isn't too useful
(it could be done with a simple local block), one can imagine
more useful applications (esp. with member functions and/or
function objects).
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/16 Raw View
"Dave Abrahams" <abrahams@motu.com> writes:
> I guess I meant to refer to the mediator pattern instead, but you've surely
> thought of things like this. A mediator is able to completely decouple the
> button and the browser page, so what's the problem?
[...]
> In my world you'd do something like this:
>
> class Button : public virtual Broadcaster
> {
> public:
> class Listener
> {
> virtual void Pressed( Button* ) = 0;
> }
> };
>
> class WebPage
> {
> void GoToNthHistoryStep( int );
> };
>
> class BrowserMediator : public virtual Receiver, private Button::Listener
> {
> void Pressed( Button* b )
> {
> if (b == back)
> page->GoToNthHistoryStep( 1 );
> ... handle some other buttons ...
> }
> }
My problem with this approach lies in BrowserMediator::Pressed. By
funelling all button presses through this single method, you create
a maintainance hassle: this method has to be edited whenever the set
of buttons changes. You also take a performance hit: you've turned
what could be an O(1) process into O(n), where n is the number of
buttons. At the cost of a map, you can get it down to O(log n),
but going below that gets pretty ugly.
To me, using an if-else if-else construction like this to avoid
needing closures feels a lot like using a case statement to avoid
needing virtual methods.
(I also don't see how your Broadcaster/Receiver can work without some
unpleasant casts, but I think some templatry could get around that.)
Compare the code above to what I would do using closures. I'll assume
here that closures can be passed by value, as they could be if implemented
in the compiler. In current C++ code, I would declare the closures
as members of various classes, and pass them by reference.
class Button
{
public:
Button( const string& name, Closure< void(*)() > toCallWhenPressed );
};
class BrowserMediator
{
void GoBack() { page->GoToNthHistoryStep( 1 ); }
void GoForth() { page->GoToNthHistoryStep( -1 ); }
};
// This goes in the whichever class owns the buttons. In your code,
// I think that would be BrowserMediator, but I'm not sure.
Button backButton, forthButton;
...
backButton( "Back", bind( mediator, &BrowserMediator::GoBack ) ),
forthButton( "Forth", bind( mediator, &BrowserMediator::GoForth ) )
That's without using a broadcaster. If you want each press of the
button to be delivered to several receivers, it's easy to insert a
broadcaster -- without modifying the Button class:
// Again, this stuff goes into the class that owns the buttons:
Broadcaster< void(*)() > backBroadcaster;
...
backButton( "Back", bind( backBroadcaster,
&Broadcaster< void(*)() >::Broadcast ) )
// Following the layout in your code, this receiver would belong in
// BrowserMediator:
Receiver< void(*)() > backReceiver;
...
backReceiver( backBroadcaster, bind( *this, &BrowserMediator::GoBack ) )
This uses a Broadcaster/Receiver implemented in terms of closures.
It can be written without any casts at all, although to shoehorn
it into current C++, you'd need to give Receiver a second template
parameter:
Receiver< void(*)(), BrowserMediator > backReceiver;
I'm quite fond of using closures this way to hook up an interface.
It's a lot like using Interface Builder, only it's compiled, strongly
typed, and portable. But it peeves me that implementations of
closures in C++ are so much worse than what a compiler could do if
they were a feature of the language.
--Lisa Lippincott
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Jean-Louis Leroy <jll@skynet.be>
Date: 1999/02/11 Raw View
> I'll call such a closure a "this-closure."
I'm almost certain that I've seen that feature refered to as 'bound
pointers-to-member function', or 'bound pointers' for short. I think
Bjarne Stroustrup uses it somewhere...
> Third, again, hassles with templating on a parameter list.
Here's the implementation for a bound pointer that takes no args. It's
not trivial to write, but it's very easy to *use*.
// comments are left as an exercise to the reader <vbg>
#include <iostream>
using namespace std;
template<class R>
class bound_pointer0
{
protected:
struct abstract_dispatcher
{
virtual R dispatch() = 0;
virtual abstract_dispatcher* clone() = 0;
};
template<class O>
struct dispatcher : abstract_dispatcher
{
dispatcher(O* o, R (O::*f)())
: o(o), f(f) { }
virtual R dispatch()
{ return (o->*f)(); }
virtual abstract_dispatcher* clone()
{ return new dispatcher(*this) ; }
O* o;
R (O::*f)();
};
abstract_dispatcher* d;
public:
template<class O>
bound_pointer0(O* o, R (O::*f)())
: d(new dispatcher<O>(o, f)) { }
bound_pointer0(const bound_pointer0& bp)
: d(new dispatcher<O>(bp.d->clone())) { }
~bound_pointer0() { delete d; }
R operator ()() { return d->dispatch(); }
};
struct Person
{
int age;
int get_age() { return age; }
};
int main()
{
Person p;
p.age = 35;
bound_pointer0<int> bp(&p, &Person::get_age);
cout << bp() << endl; // 35!
return 0;
}
This implementation uses standard C++ AFAIK. If you're willing to take
a couple of very reasonable risks, like assuming that all
pointers-to-member have the same size, you can do away with the new(),
delete() and clone().
By playing tricks with partial specialization it would even be possible
to replace bound_pointer0, bound_pointer1...bound_pointern with a
single bound_pointer template.
> On balance, I think this-closures would be a worthy addition to C++.
The very fact that bound pointers can be implemented in current C++
demonstrates that they have no place in the *language*, but rather in a
library. That's the C++ way, make the language powerful enough that
much functionality can be transfered to libraries. Contrat FORTRAN's
built-in (and not-so-extensible) I/O support with C++'s (highly
extensible) iostream library.
Borland/Inprise closures are probably different story. The
bound_pointer objects cannot be passed to code written in other
languages in place of plain function pointers. Borland is probably
trying to solve a MS-Windows specific problem by hacking the language.
It's not the first time. Anybody remembers dispatch tables? And what's
become of them??
And if you're willing to go non-portable to better support some
platform, there are other options anyway (like creating code on the
fly).
Jean-Louis Leroy
http://ourworld.compuserve.com/homepages/jl_leroy
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/12 Raw View
I wrote:
> A this-closure breaks a dependency that shows up regularly in
> user interfaces. Here's a typical example: in a web browser, there's
> a button "back," which causes the display to change in a particular
> way. Ideally, the "back" button is an instance of a button class
> which knows nothing about web browsing. Also, ideally, the class
> which implements the "back" function does not know that the function
> will be triggered by a button. A closure breaks both dependencies.
"Dave Abrahams" <abrahams@motu.com> replied:
> This problem is much better solved by the classic "model-view-controller"
> pattern (see smalltalk).
In C++, this pattern doesn't break the dependency; it just moves it. If
there's a back button, the back function must be implemented somewhere.
It could be
a) folded into some larger function,
b) written as a nonmember function taking one parameter, or
c) written as a member function of some class X taking no parameters.
I think we agree that (c) is preferable; using a model-view-controller
pattern just changes the class denoted by X.
In C++, without using some closure mechanism, the button code depends
both the name of the method and the type of (a base class of) the
class X where the implementation (or a forwarder) lies.
In Smalltalk, this dependency on the type X is indeed broken, because
one may attempt to apply any method to any object. I think the
dependency on the method name can also be broken in Smalltalk, but
I'm too rusty to remember how.
"Dave Abrahams" <abrahams@motu.com> also writes:
> What happens if the button needs to communicate with a number of different
> objects? If it is hard-coded to call this one closure, you're stuck... or
> you have a lot of grunt work to do to distribute the calls. You can use
> "model-view-controller" along with a "broadcaster/receiver" pattern to solve
> some of these problems more elegantly.
I'm glad you brought that up. A C++ broadcaster/receiver must either
use a closure mechanism, or limit each receiver to a single, uniformly-
named receiving method. In our example, the latter choice makes it
difficult for the same object to implement both the "back" and "home"
functions.
But with closures, the button could simply be bound to the broadcasting
method of a broadcaster.
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/12 Raw View
Jean-Louis Leroy <jll@skynet.be> wrote:
> Here's the implementation for a bound pointer that takes no args. It's
> not trivial to write, but it's very easy to *use*.
He provides a simple implementation similar to the one I use, and
which I described in an earlier article. Of the problems I described
in my article, it shares these:
a) It's remarkably inefficient in both space and time compared
to what could be done with a language mechanism.
b) It requires a public copy constructor for the return type.
By restricting his attention to functions with no parameters,
he ignores these problems:
c) My implementation won't work with parameter lists of arbitrary
length, although it can be implemented for any finite set of
lengths.
d) My implementation requires public copy constructors for all
parameters. On most architechtures I know of, the copies
my implementation introduces cannot be optimized away.
Unlike my implementation, his uses a pimpl idiom which allows it to
be passed by value, but introduces the possibility that the binding
operation may throw. For those bothered by use of the heap (like me)
he suggests:
> This implementation uses standard C++ AFAIK. If you're willing to take
> a couple of very reasonable risks, like assuming that all
> pointers-to-member have the same size, you can do away with the new(),
> delete() and clone().
Not just size, but alignment, too. Of course, any implementation of
the language mechanism would move this implementation dependency
into the compiler, where it belongs.
> The very fact that bound pointers can be implemented in current C++
> demonstrates that they have no place in the *language*, but rather in a
> library.
If I knew of a more adequate implementation in the language, I would
agree. But I think this features falls in the same realm as the
"inherited" keyword or (formerly) RTTI. There are approximate solutions
in the language, but a built-in solution would work better. Whether
the improvement is worth the cost of the feature is certainly
open to debate.
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/12 Raw View
Dave Abrahams wrote:
> Lisa Lippincott wrote in message ...
> >A this-closure breaks a dependency that shows up regularly in
> >user interfaces. Here's a typical example: in a web browser, there's
> >a button "back," which causes the display to change in a particular
> >way. Ideally, the "back" button is an instance of a button class
> >which knows nothing about web browsing. Also, ideally, the class
> >which implements the "back" function does not know that the function
> >will be triggered by a button. A closure breaks both dependencies.
>
> This problem is much better solved by the classic "model-view-controller"
> pattern (see smalltalk). The design you describe still has a strong
> dependency on the nature of the button, even when you use this-closures. For
> example, suppose you wanted to use a slider to select how far back in the
> history to go instead of a button (granted, that might be a stupid interface
> for a browser, but humor me). You'd need a different function taking an int
> parameter. Connecting the button directly to the browser through this
> closure still depends on the assumption that the browser has an appropriate
> "back" function and that the button is configured to call such a closure.
> Suppose buttons have some other information to pass to their closure? It
> wouldn't match the simple, parameterless "back" function.
The button specifies the closure that can be hooked to by anything else. The
parameters of the closure are specified by the "Button" class and is made public
so that someone else can hook to it. It does not have to be a paramaterless
function at all. If a slider existed it would specify its own closure. An object
can even specify more than one closure for different things that it may "do". By
"do" it means that an object can trigger any event for whatever reasons it
specifies; the event does not have to come directly from the user, it can be
called anywhere from within the object. There is no problem as long as the
closure is documented just like any other class member function. The closure is
there to be hooked and the "Button" class in this example is not concerned who
hooks it.
> What happens if the button needs to communicate with a number of different
> objects? If it is hard-coded to call this one closure, you're stuck... or
> you have a lot of grunt work to do to distribute the calls. You can use
> "model-view-controller" along with a "broadcaster/receiver" pattern to solve
> some of these problems more elegantly.
Whoever hooks the closure can obviously chain to the next caller from within the
hooking code. That's not what I would call alot of grunt work. In C++ Builder
this technique is not used directly because of the nature of the GUI classes (
VCL or Visual Component Library ) that C++ Builder uses but in another
environment this technique could be used. In the C++ Builder model the "Button"
is not trying to communicate to anything in particular; it is just triggering an
"event", defining an event in its broadest terms. It's up to the event handler
to decide what to do when the event occurs. Furthermore the event handler can
hook to more than one event of the same prototype coming from 2 or more
different sources. In C++ Builder it is customary, though not mandatory, that
the first parameter passed to the event handler is a pointer to a base class for
the event creating object. This assumes that if more than one event with the
same prototype can be hooked to, the event creators are all descended from the
same base class. In C++ Builder all VCL objects inherit from the same base
class.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Edward Diener <eddielee@abraxis.com>
Date: 1999/02/12 Raw View
Jean-Louis Leroy wrote:
> Borland/Inprise closures are probably different story. The
> bound_pointer objects cannot be passed to code written in other
> languages in place of plain function pointers. Borland is probably
> trying to solve a MS-Windows specific problem by hacking the language.
The argument for this type of "closure" goes well beyond a hack to solve an
MS-Windows specific problem. If C++ can have pointers to global functions and
pointers to member functions of a specific class, why could it not have
pointers to member functions of any class. Is there some reason why this idea
is so "far out" as to be almost unthinkable. It seems like a natural idea to
me. As a matter of fact if C++ had a type of pointer that could point to the
appropriate function whether it was a global function or a class member
function, I would consider this as also a good idea that adds even more
flexibility to the language. Perhaps the idea that a function pointer should
be restricted to either being to a global function or a particular class's
member function can be seen as a limitation of the language.
Whether this type of "closure" can be passed to other languages is utterly
irrelevant. Is C++ supposed to not implement good ideas because other
languages are poorer in power and flexibility.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Christopher Eltschka <celtschk@physik.tu-muenchen.de>
Date: 1999/02/12 Raw View
Lisa Lippincott wrote:
[...]
> > The very fact that bound pointers can be implemented in current C++
> > demonstrates that they have no place in the *language*, but rather in a
> > library.
>
> If I knew of a more adequate implementation in the language, I would
> agree. But I think this features falls in the same realm as the
> "inherited" keyword or (formerly) RTTI. There are approximate solutions
> in the language, but a built-in solution would work better. Whether
> the improvement is worth the cost of the feature is certainly
> open to debate.
Maybe it would be a good addition to the standard library, since
- it can be implemented with standard C++
- it can be optimized very much by using nonstandard code
- it is of relatively general use (it's asked for quite often
in the newsgroups)
Note that anything in the standard library may make use
of any non-conforming method or compiler extension the
implementor likes. For example, The C++Builder class would
for sure just make a thin encapsulation of their closures.
Another implementation may use ugly typecasts which are not
guaranteed to work by the standard, but which _do_ work
on the compiler the standard library is written for.
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Jean-Louis Leroy <jll@skynet.be>
Date: 1999/02/12 Raw View
> b) It requires a public copy constructor for the return type.
You mean the type returned by an invocation of the bound pointer? How is
that a problem? Same goes with calling an ordinary function through an
ordinary pointer...
> By restricting his attention to functions with no parameters,
> he ignores these problems:
>
> c) My implementation won't work with parameter lists of arbitrary
> length, although it can be implemented for any finite set of
> lengths.
That was just an example. A complete implementation would contain
bound_pointerXXX for at least 5 args, with a means of generating them for
any number of parameters.
Jean-Louis Leroy
http://ourworld.compuserve.com/homepages/jl_leroy
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: lisa_lippincott@advisories.com (Lisa Lippincott)
Date: 1999/02/13 Raw View
I wrote, regarding a closure-on-this mechanism which Jean-Louis Leroy
<jll@skynet.be> gave in an earlier article:
> b) It requires a public copy constructor for the return type.
Jean-Louis Leroy asks:
> You mean the type returned by an invocation of the bound pointer? How is
> that a problem? Same goes with calling an ordinary function through an
> ordinary pointer...
Consider the following:
class NoCopy
{
private: NoCopy( const NoCopy& );
public: NoCopy( int );
};
class RestrictedCopy
{
friend void Foo( RestrictedCopy (*)(NoCopy) );
private: RestrictedCopy( const RestrictedCopy& );
};
void Foo( RestrictedCopy (*f)(NoCopy) )
{
RestrictedCopy r( (*f)( NoCopy( 1 ) ) );
}
The code above has no access errors. If you replace the function
pointer with a closure, it should still have no access errors.
But, to my knowledge, every closure-on-this mechanism written in C++
will introduce extra copies of the parameters and return value
in other scopes, leading to access errors.
Regarding the limitation on the number of parameters,
Jean-Louis Leroy writes:
> That was just an example. A complete implementation would contain
> bound_pointerXXX for at least 5 args, with a means of generating them for
> any number of parameters.
I'd be a little unhappy if the C++ standard said that function pointers
could be used for up to five arguments. And if, as some have suggested,
a closure mechanism were added to the standard library, I think your
"means of generating them" would run into problems with the one
definition rule.
To some, these may be minor problems with a library implementation.
But I think they begin to answer the "you can implement it just as
well in a library" argument.
--Lisa Lippincott
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Dave Abrahams" <abrahams@motu.com>
Date: 1999/02/14 Raw View
Lisa Lippincott wrote in message ...
>"Dave Abrahams" <abrahams@motu.com> replied:
>
>> This problem is much better solved by the classic "model-view-controller"
>> pattern (see smalltalk).
>
>In C++, this pattern doesn't break the dependency; it just moves it.
I guess I meant to refer to the mediator pattern instead, but you've surely
thought of things like this. A mediator is able to completely decouple the
button and the browser page, so what's the problem?
>If
>there's a back button, the back function must be implemented somewhere.
>It could be
> a) folded into some larger function,
> b) written as a nonmember function taking one parameter, or
> c) written as a member function of some class X taking no parameters.
>
>I think we agree that (c) is preferable; using a model-view-controller
>pattern just changes the class denoted by X.
>
>In C++, without using some closure mechanism, the button code depends
>both the name of the method and the type of (a base class of) the
>class X where the implementation (or a forwarder) lies.
I don't see that as a dependency. In my world you'd do something like this:
class Button : public virtual Broadcaster
{
public:
class Listener
{
virtual void Pressed( Button* ) = 0;
}
};
class WebPage
{
void GoToNthHistoryStep( int );
};
class BrowserMediator : public virtual Receiver, private Button::Listener
{
void Pressed( Button* b )
{
if (b == back)
page->GoToNthHistoryStep( 1 );
... handle some other buttons ...
}
}
You could say that the button implementation "depends on both the name of
the method and the type of Button::Listener", but since Button::Listener is
really part of the definition of Button it's not much of a dependency.
>"Dave Abrahams" <abrahams@motu.com> also writes:
>> What happens if the button needs to communicate with a number of
different
>> objects? If it is hard-coded to call this one closure, you're stuck... or
>> you have a lot of grunt work to do to distribute the calls. You can use
>> "model-view-controller" along with a "broadcaster/receiver" pattern to
solve
>> some of these problems more elegantly.
>
>I'm glad you brought that up. A C++ broadcaster/receiver must either
>use a closure mechanism, or limit each receiver to a single, uniformly-
>named receiving method.
Mine doesn't have that limitation. Broadcasting in the button above would
take the form: Broadcast( Listener::Pressed, this ), where Broadcast is one
of a family of template member functions.
>In our example, the latter choice makes it
>difficult for the same object to implement both the "back" and "home"
>functions.
>
>But with closures, the button could simply be bound to the broadcasting
>method of a broadcaster.
Now I'm really lost. Shouldn't the button be doing the broadcasting? If
anything I would expect you to bind the receiver, which would be a component
which responded to the button, such as the web page or the mediator.
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Lisa Lippincott <lisa_lippincott@advisories.com>
Date: 1999/02/10 Raw View
I'll take a stab at clearing up the fog around closures. First, here's
my lack of credentials: I haven't used anything LISP-like in many years,
and I've never used any of the Borland or Inprise products. But,
in the course of user interface programming, and working with parsers,
I have wanted closures in C++, and I've tried working around their
absence.
Mathematically, a closure is the binding of parameter values to a
function. Here's a basic example, where add3 is a closure of add,
binding the value 3 to the parameter b:
int add( int a, int b ) { return a + b; }
int add3( int a ) { return add( a, 3 ); }
I think this usage goes back to the early work in recursive
functions that influenced the design of LISP.
LISP has a very general notion of closure, which (as others have
covered) is entirely impractical in C++.
The Borland/Inprise closure is more limited, in that only the "this"
parameter of a method call can be bound. To avoid confusion,
I'll call such a closure a "this-closure."
A this-closure breaks a dependency that shows up regularly in
user interfaces. Here's a typical example: in a web browser, there's
a button "back," which causes the display to change in a particular
way. Ideally, the "back" button is an instance of a button class
which knows nothing about web browsing. Also, ideally, the class
which implements the "back" function does not know that the function
will be triggered by a button. A closure breaks both dependencies.
There are three parts to a this-closure mechanism. First, there's
a new family of types "return-type closure( parameter-list )"
In the example above, the button class would have a member
void closure toCallWhenHit()
Second, a binding operation, which combines an appropriately
cv-qualified object with a method of that object, producing a
closure. In the example above we might use
make_closure( webEngine, &WebEngineType::GoToPreviousPage )
as a parameter to the button's constructor. (I think Borland
has a prettier syntax, but I don't know if it's a practical one.)
Third, a function call operation. In the example, the button's code
would use this expression when the button was hit:
toCallWhenHit()
So how's it implemented? In most C++ implementations we can make
two assumptions. First, after any virtual function lookup, a method
call is just a function call with a hidden "this" parameter.
Second, the way parameters are passed to that function does not
depend on the type of "this", or on whether the method is virtual.
Thus a typical implementation of the closure type can be a pair: the
"this" pointer, and the address of the function to call. (An actual
function pointer, as opposed to a pointer-to-method.)
The make_closure operation produces this type by doing the usual
(virtual or non-virtual) function lookup and adjusting the "this"
pointer as necessary.
The function call operation just adds the already-calculated "this"
pointer to the parameter list, and calls the already-looked-up
function.
I gather that the Borland/Inprise implementation also allows for
a plain function pointer to be converted to a closure, marking
the result with a null "this" pointer. I've never needed that
conversion; I suspect it makes the implementation less efficient;
and I suspect it can be easily worked around if it's left out.
So why can't the above implementation of this-closures be coded up
in current C++? There are three problems that I can see:
First, the two assumptions above are commonly satisfied,
but not guaranteed.
Second, the programmer's only access to the virtual
function lookup mechanism is in calling methods.
Third, the workarounds for templating on arbitrary
parameter lists are cumbersome.
Nevertheless, one can produce an approximation. Jerry Coffin
<jcoffin@taeus.com> suggests:
> My initial stab at things would probably be to declare a base class
> functor object. When I wanted to use a "closure"-like device, I'd use
> a derivative of this functor instead of a member function. Since a
> member object doesn't require a different sort of pointer than a non-
> member object, a pointer to the base class could invoke the function
> call operator in any derived object, regardless of what it happened to
> be a member of (or even if it wasn't a member of anything.)
That describes what I use now. I have a base class functor, templated
on parameter list and return value, with a pure virtual function call
operator. Derived classes, templated further on the type of "this",
contain a "this" pointer and a pointer-to-member. Such an approach
has four disadvantages over the built-in implementation above:
First, closures with differing types for "this" have
different types, despite sharing a common base.
Therefore they have to passed around by reference
rather than value, and the objects which tie the user
interface to the implementation end up full of
functor objects for the user interface objects to
reference.
Second, the built-in version is much more efficient.
Its closures would be less than half the size, and
calling them would rival the speed of calling through a
function pointer. The template implementation
can take more time than two virtual calls. In situations
where virtual lookups are expensive (e.g. CORBA)
the built-in mechanism could be a particularly big win.
Third, again, hassles with templating on a parameter list.
Fourth, the derived functor class needs access to the
copy-constructors and destructors for all the parameters
and the return value. In the built-in solution, the
parameters need not even have copy constructors.
On balance, I think this-closures would be a worthy addition to C++.
--Lisa Lippincott
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Dave Abrahams" <abrahams@motu.com>
Date: 1999/02/11 Raw View
Lisa Lippincott wrote in message ...
>A this-closure breaks a dependency that shows up regularly in
>user interfaces. Here's a typical example: in a web browser, there's
>a button "back," which causes the display to change in a particular
>way. Ideally, the "back" button is an instance of a button class
>which knows nothing about web browsing. Also, ideally, the class
>which implements the "back" function does not know that the function
>will be triggered by a button. A closure breaks both dependencies.
This problem is much better solved by the classic "model-view-controller"
pattern (see smalltalk). The design you describe still has a strong
dependency on the nature of the button, even when you use this-closures. For
example, suppose you wanted to use a slider to select how far back in the
history to go instead of a button (granted, that might be a stupid interface
for a browser, but humor me). You'd need a different function taking an int
parameter. Connecting the button directly to the browser through this
closure still depends on the assumption that the browser has an appropriate
"back" function and that the button is configured to call such a closure.
Suppose buttons have some other information to pass to their closure? It
wouldn't match the simple, parameterless "back" function.
What happens if the button needs to communicate with a number of different
objects? If it is hard-coded to call this one closure, you're stuck... or
you have a lot of grunt work to do to distribute the calls. You can use
"model-view-controller" along with a "broadcaster/receiver" pattern to solve
some of these problems more elegantly.
-Dave
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: rae@alias.com (Reid Ellis)
Date: 18 Jun 91 23:15:00 GMT Raw View
If closures were restricted to virtual methods, they could be
implemented as pointers to vtable entries, couldn't they? But then,
that restriction is a bit much.
Just a thought..
Reid
--
Reid Ellis _|\ |V| /|_
rae@utcs.toronto.edu || rae@alias.com \ \| |/ /
CDA0610@applelink.apple.com || +1 416 362 9181 [work] >_______<
!