Gyro@Reasoning.COM (04/12/91)
Here is a draft of a proposal I intend to submit to the C++ committee. Your comments are solicited. (Also could someone please post the procedure for formally submitting a proposal to the committee? Thanks.) [410 lines follow] PROPOSAL FOR CLOSURES IN C++ DRAFT 12 April 1991 Scott L. Burson (Gyro@Reasoning.COM) 0. INTRODUCTION =============== The following commentary appears on p. 71 of Ellis & Stroustrup, in the discussion of pointers to member functions: Naturally, it would be possible to generalize the notion of a pointer to member to allow bound pointers, such as `ptr_to_obj->* 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. I beg, nay implore, the committee to reconsider this issue. In what follows I will argue: 1) That such "bound pointers" (I will use "closure", the traditional Lisp term) have a number of uses for which the nearest substitutes are substantially less satisfactory. 2) That implementing closures at the user level using the existing mechanisms of C++, while possible, is unsatisfactorily verbose. 3) That closure creation and invocation operations fit extremely neatly, syntactically and semantically, into the language. 4) Contrary to the above, that the natural implementation of closures is no more "outside the realm of traditional C techniques" than that of many existing C++ constructs, and that the costs are well within the bounds of acceptability. 5) Contrary to the above, that quite the opposite of opening a can of worms or of adding a wart to the language, the semantics of closures very neatly fill a hole in the current definition of C++. I will venture to guess that a proposal of this nature, even if it is thought to be of some merit, will not be looked upon enthusiastically by those members of the committee eager to reach closure, so to speak, in the standardization process. I would not take the time to present such a proposal did I not believe C++ likely to become the most widely used programming language ever, and did I not think it likely that the omission of closures will ultimately cost users far more, in inelegant code, unnecessary casting, and excessive module interdependencies, than it will cost to include it at this point. 1. PROPOSED FUNCTIONALITY ========================= But first, before I press this argument, let me say quite concretely what I am proposing. There are actually two logically separable subproposals; while I personally prefer to think of them as a package, it would be possible to adopt the first without the second, and the committee might well prefer to do it that way. 1) Proposal 1 introduces a new space of types `closure of T' where T is any function signature. (The question of declaration syntax will be addressed below.) A closure of T can be created in two ways. The first way is by evaluating the name of (or applying the unary `&' to) the name of a global function or static member function of signature T; the resulting object supports a single operation, that of function invocation, and when so invoked behaves exactly as a pointer to T, created in the same way, would have behaved. The second way to create a closure of T is, within the body of a nonstatic member function, to evaluate the name of (or apply unary `&' to) the name of a nonstatic member function (call it F) of signature T. A closure created in this latter way, when invoked, performs the operation F, passing it the same value for `this' as was in force at the point of *creation* of the closure; that is, it invokes the member function F on the same object as the one that created the closure (F is said to have been "closed over" this object). A closure over an object can be validly invoked only so long as the object continues to exist. Note that unlike pointers to member functions, the various closure types are distinguished only by their signatures; a value of type `closure of T' can be created by any class, or by no class (when a global or static member function is involved). 2) Proposal 2 unifies the domain `closure of T' with that of `pointer to function of signature T'. That is, under proposal 2, the same syntax that has been interpreted as declaring a pointer to function of signature T will now be interpreted as declaring a closure of T. If proposal 1 is adopted but not proposal 2, it will be necessary to invent a declaration syntax for closures. I prefer to avoid doing so unless and until it may be seen to be the sense of the committee that this is a likely outcome. It may bear pointing out that the rule "a closure over an object can be validly invoked only so long as the object continues to exist" is of the same form as the corresponding rule concerning a normal pointer which points to a member variable of an object. For this reason, I consider it a natural approach to take in C++. It is also worth pointing out that this rule constrains the semantics of closures in such a way that no unusual implementation techniques are required for them. (I suspect that when Ellis and Stroustrup mentioned "implementation techniques outside the realm of traditional C techniques" they had in mind the fact that closures in, e.g., Lisp have indefinite extent, and therefore their implementation is intimately tied to the fact that Lisp supports garbage collection.) 2. WHY CLOSURES ARE USEFUL ========================== The key to the value of closures was mentioned above: they permit a kind of abstraction that is not otherwise available in C++, by making it possible to refer to an operation with regard only to its signature, without specifying what class it is a closure over, or indeed even whether it is a closure over an object at all. So, for an (admittedly a bit contrived) example (which assumes both subproposals are accepted): EXAMPLE 1 ========= class counter { int total; int count_up(int n) { return total += n; } int count_down(int n) { return total -= n; } public: counter(int init) { total = init; } int (*count_up_fn())(int) { return &count_up; } int (*count_down_fn())(int) { return &count_down; } }; class doubling_counter { int current; int add_next(int n) { current *= 2; return current += n; } public: doubling_counter(int init) { current = init; } int (*add_next_fn())(int) { return &add_next; } }; int twice(int a) { return a * 2; } void print_some(int (*pf)(int), int n) { for (int i = 0; i < n; ++i) cout << pf(i) << " "; cout << "\n"; } mumble() { print_some(&twice, 5); counter c(0); print_some(c.count_up_fn(), 5); print_some(c.count_down_fn(), 10); doubling_counter dc(0); print_some(dc.add_next_fn(), 7); } Output of mumble(): 0 2 4 6 8 0 1 3 6 10 10 9 7 4 0 -5 -11 -18 -26 -35 0 1 4 11 26 57 120 It can be seen from the extreme elegance of this code that closure creation and invocation can fit very neatly indeed into the syntax and semantics of C++. Here, by way of contrast, is how the same example would be written using a user-defined closure class; this shows what one would have to do to write this code in the same style in C++ as it currently exists. EXAMPLE 2 ========= class closure_int_int { public: virtual int operator()(int arg) = 0; }; class global_closure_int_int: public closure_int_int { int (*fn)(int); public: global_closure_int_int(int (*_fn)(int)) { fn = _fn; } int operator()(int arg) { return fn(arg); } }; class counter; class counter_closure_int_int: public closure_int_int { int (counter::* pmf)(int); counter& c; public: counter_closure_int_int(counter& _c, int (counter::*_pmf)(int)) : c(_c), pmf(_pmf) {} int operator()(int arg) { return (c.*pmf)(arg); } }; class counter { int total; int count_up(int n) { return total += n; } int count_down(int n) { return total -= n; } public: counter(int init) { total = init; } counter_closure_int_int count_up_fn() { return counter_closure_int_int(*this, &counter::count_up); } counter_closure_int_int count_down_fn() { return counter_closure_int_int(*this, &counter::count_down); } }; class doubling_counter; class doubling_counter_closure_int_int: public closure_int_int { int (doubling_counter::* pmf)(int); doubling_counter& c; public: doubling_counter_closure_int_int (doubling_counter& _c, int (doubling_counter::*_pmf)(int)) : c(_c), pmf(_pmf) {} int operator()(int arg) { return (c.*pmf)(arg); } }; class doubling_counter { int current; int add_next(int n) { current *= 2; return current += n; } public: doubling_counter(int init) { current = init; } doubling_counter_closure_int_int add_next_fn() { return doubling_counter_closure_int_int (*this, &doubling_counter::add_next); } }; int twice(int a) { return a * 2; } void print_some(closure_int_int& cl, int n) { for (int i = 0; i < n; ++i) cout << cl(i) << " "; cout << "\n"; } mumble() { global_closure_int_int cl_twice(&twice); print_some(cl_twice, 5); counter c(0); counter_closure_int_int cl_up = c.count_up_fn(); print_some(cl_up, 5); counter_closure_int_int cl_down = c.count_down_fn(); print_some(cl_down, 10); doubling_counter dc(0); doubling_counter_closure_int_int dc_add = dc.add_next_fn(); print_some(dc_add, 7); } (I believe this code to be correct, but haven't tested it -- it shows the general idea, anyway.) There are several things to be pointed out about this example: 1) It takes 78 lines to do what Example 1 did in 35. It is also much less readable. 2) One base class, in this case `closure_int_int', must be defined for each signature of closure to be created. 3) One derived class must be created for each class to be closed over, plus an additional one for "closures" of global functions if desired. (Derived classes of, e.g., `counter' could share `counter_ closure_int_int', however.) In practice, therefore, it is unlikely that anyone would actually write a piece of code like Example 2, even if they had a good reason to. An opportunity for potentially valuable abstraction would therefore be missed. Let's look at some examples of applications for closures: 1) General-purpose higher order functions (functions that take functions as arguments). `print_some' above is a fairly uninteresting example, but it is not hard to think of better ones. Consider for instance a class for holding sequences of values; it might well support an operation, perhaps called `image', that takes a closure and returns a new sequence of the same type as itself, where each element is the value the closure returns when called on the corresponding element of the original sequence. 2) A general-purpose error handling facility, which needs to be able to perform operations like restarting the erroneous computation, and which clearly should be able to be written and compiled independently of the classes it may eventually be invoked from. 3) Closures are handy whenever there is a reason to write the control structure of a piece of code in a way that is "upside down" with respect to the most natural form. Macintosh programmers, for instance, could use closures to write a general event loop facility, which would maintain a dynamically updatable collection of closures each with a representation of the kinds of events that should cause it to be invoked. To summarize, the ultimate value that closures can contribute to C++ programs seems to boil down to two primary areas: 1) In any case where pointers to functions are being used, closures add a very substantial generalization. 2) Because the type of a closure is independent of the types of the objects that may create it, closures are a tool for reducing the dependencies between C++ modules. The bottom line, then, is code that is more elegant and more modular. 3. IMPLEMENTATION CONSIDERATIONS ================================ Here I attempt to show that a closure facility such as I propose can be implemented with perfectly acceptable efficiency, in the C++ tradition, and with minimal changes to an imagined typical C++ compiler. A closure can be implemented as a pair of: 1) a pointer to function in the C sense, and 2) a pointer which will be passed as the value of `this'. In a typical C++ implementation, this translates into two 32-bit words, where a C pointer to function takes only one word. Thus, the primary costs of changing all pointers to functions into closures are a doubling of the space they require, plus the additional time required to move the extra word when assigning them to variables or passing them to or from functions. However, a closure will (typically) be only as large as a `double', which even in the early 16-bit implementations of C was not considered too large to be frequently passed to and returned from functions. The question then arises: how do we deal with the slightly different calling protocols for top-level functions and member functions, that is, the fact that the C functions that implement member functions expect a `this' pointer to be passed as an additional argument, while top-level functions do not? Several possibilities suggest themselves: 1) Arrange that all top-level functions shall take an additional argument, (typically) at the beginning of the argument list, which they ignore. There is then no need for the code that invokes a closure to make a runtime determination of which kind is being called. 2) Do not change the conventions for top-level functions; instead, when creating a closure of a top-level function, be sure that the value stored in the `this' pointer is null. Then have the closure invocation sequence check that pointer, and use different calling protocols depending on whether it is null (top-level function case) or not (member function case): (closure.this ? (*closure.pfn)(closure.this, <args>) : (*closure.pfn)(<args>)) 3) Establish a convention for member function invocation such that the presence of the `this' argument does not change the protocol for passing any other arguments; that is, so that `this' is passed in some way outside the normal argument passing mechanisms. Then the closure invocation sequence need make no runtime distinction between the top-level and member function cases. 4) For each top-level function, define an interface function that takes the `this' argument (typically) at the beginning of its argument list, and then calls the top-level function with its correct argument list (i.e., without `this'). Then the closure invocation sequence need make no runtime distinction between the top-level and member function cases. Of the first three, I will venture to guess that (1) will be the least popular, although there are high-performance Scheme implementations that work exactly this way. Besides the overhead of adding an argument to every top-level function, it doesn't work for top-level functions with C linkage, where the option of adding an argument is not available (although approach (4) could perhaps be used to cover that case). Native C++ implementations will likely select (3), it seems to me, although it is not restricted to them; Cfront, for instance, could pass `this' in a global variable, using an explicit (i.e. C source level) caller-saves protocol. My guess, however, is that (2) will be the choice for translator-based implementations such as Cfront. (2) can work very nicely also for native implementations that pass all arguments on the stack: the generated code can optionally push the `this' pointer, then continue with the normal calling sequence. (4) initially seems attractive in this context, but it is incapable of handling variable-length argument lists (though certain native implementations may be able to solve this problem); perhaps, in order explicitly to permit implementors the option of using it, closures whose signatures include the ellipsis should be disallowed. 4. CONCLUSION ============= I have presented a proposal for the introduction of closures into C++, and a closely related proposal to the effect that they should replace pointers to functions as they currently exist. I have shown that they permit a form of abstraction that is otherwise unavailable in the language. I have further shown that they are neither difficult nor expensive to implement in the form proposed. I therefore propose that closures be added to C++.
chased@rbbb.Eng.Sun.COM (David Chase) (04/13/91)
There's one more way to implement closures, though it is less workable in this age of parameter-passing in registers and split (inconsistent) instruction and data caches -- one can generate code at run time to implement a closure, and this pass a "closure pointer" in the same style currently used to implement a "function pointer". Thomas Breuel (sp? tmb@prep.ai.mit.edu, I think) originally gave me this tip for implementing nested functions in Modula-3, and it worked quite well for the 68000. David Keppel (pardo@cs.washington.edu) has been exploring use of run-time code generation on machines with less friendly caches. And yes, I think closures are a good idea. I have no special sympathy for existing code that uses pointers to member functions; first, I've never come across such code, and second, it wouldn't be the first time that a change to the language broke existing code. I'm certain that I'd have more use for this than for (say) multiple inheritance (why so? because I've needed to use this a couple of times already and simulated it clumsily, and because I haven't ever used multiple inheritance, even though it is already in the language.) Also, the implementation techniques suggested are not that bizarre -- consider what is typically done to implement multiple inheritance, or what will be done to implement exceptions (it's either tricky, or you lobotomize your optimizer, and it'll happen whenever you have a stack-allocated object that has an associated destructor). David Chase Sun
gyro@kestrel.edu (Scott Layson Burson) (04/13/91)
In article <11489@exodus.Eng.Sun.COM> chased@rbbb.Eng.Sun.COM (David Chase) writes: >There's one more way to implement closures, though it is less workable >in this age of parameter-passing in registers and split (inconsistent) >instruction and data caches -- one can generate code at run time to >implement a closure, and this pass a "closure pointer" in the same >style currently used to implement a "function pointer". This possibility occurred to me, but I don't think it's a good idea, because it means closure creation would require heap allocation; in C++ (unlike Lisp) it is really not acceptable for a fundamental language construct to use heap allocation for its implementation. Consider also that not only would they have to be heap-allocated, but a reference count scheme (like the `string' example in Stroustrup's original C++ book -- sorry, I don't have the page number handy) would be required to ensure that the allocated closures would be freed at the right time. This is really quite a lot of overhead just to keep closure pointers one word long; I don't think it could ever be a worthwhile tradeoff. (Note that the trick doesn't require heap allocation for nested functions, because the "object" (stack frame) being closed over is itself necessarily stack-allocated.) >And yes, I think closures are a good idea. I have no special sympathy >for existing code that uses pointers to member functions; first, I've >never come across such code, and second, it wouldn't be the first time >that a change to the language broke existing code. I'm certain that >I'd have more use for this than for (say) multiple inheritance (why >so? because I've needed to use this a couple of times already and >simulated it clumsily, and because I haven't ever used multiple >inheritance, even though it is already in the language.) Oh dear, I left out a whole section from the proposal! To summarize: the inclusion of closures as I propose them would break NO existing portable C++ code (I'll explain the "portable" in a moment). First, closures and pointers to member functions do not overlap syntactically. The notation for creating a pointer to member function is not the simple `&f' (or just `f'); it is `&C::f' where C is the class; similarly, the notation for declaring one also includes the `C::'. Second, the language does not currently define `&f' (or just `f') when f is a member function of a class. Thirdly, closures of normal (nonmember) functions would behave exactly like pointers to those functions do currently. The only thing that would break is conversions between pointers to functions and `void*', but these are not portable anyway (E&S p. 36: "A pointer to function may be converted to a `void*' provided a `void*' has sufficient bits to hold it.") Personally, I would like to see pointers to members deleted from the language, but this issue is orthogonal to the closure proposal and can be considered entirely independently. -- Scott Gyro@Reasoning.COM
bs@chaos.cs.brandeis.edu (Benjamin Schreiber) (04/23/91)
(before you ask, the answer is no: I am NOT Bjarn Stroustroup; I just happen to have the same alias) In article <1991Apr12.081539.22690@kestrel.edu> Gyro@Reasoning.COM writes: > 2) Proposal 2 unifies the domain `closure of T' with that of `pointer > to function of signature T'. That is, under proposal 2, the same > syntax that has been interpreted as declaring a pointer to > function of signature T will now be interpreted as declaring a > closure of T. > >If proposal 1 is adopted but not proposal 2, it will be necessary to >invent a declaration syntax for closures. I prefer to avoid doing so >unless and until it may be seen to be the sense of the committee that >this is a likely outcome. Closures are an excelant idea. However, I think that they should be kept distinct from pointers to functions and pointers to member functions. My primary argument for keeping them distinct from normal pointers to functions has to do with functions of "C" linkage: if yoou attempt to give it a closure, it will choke. If the concepts are indeed kept distinct, a clearly distint syntax is necessary. My suggestion would be to use a unary && (an extention of taking the address of a function). This operator would also be used in the declaration. Example: class to_be_closed { private: int data; public: int method(int); to_be_closed(int d) {data = d;}; }; int f(int c) // A function to be close over { return c; } extern "C" { int g(int); int use(int (*)()); } void do_stuff(void) { int &&closure(int); // Declaration of a closure to_be_closed obj(5); closure = &&obj.method; // take closure of method over obj closure = obj.method; // same, since there is no other meaning closure(5); // Invoke the closure closure = &&f; // Take closure of global method closure = f; // Error: f alone equivelant to &f use(closure); // Error: can't pass closure in place of fptr use(f); // OK: pointer to function expected } I hope this example is clear (and not error riddled; my editor is having problems...) If you don't want to take me seriously, you don't have to; I'm only an undergraduate with an enthusiasm for C++. -- Benjamin Schreiber bs@cs.brandeis.edu
gyro@kestrel.edu (Scott Layson Burson) (04/26/91)
In article <1991Apr23.032208.3264@news.cs.brandeis.edu> bs@chaos.cs.brandeis.edu (Benjamin Schreiber) writes: >Closures are an excelant idea. However, I think that they should be >kept distinct from pointers to functions and pointers to member functions. >My primary argument for keeping them distinct from normal pointers to >functions has to do with functions of "C" linkage: if yoou attempt to >give it a closure, it will choke. Thanks for mentioning this; it's a serious problem that I hadn't fully addressed. It was part of the reason why I mentioned the possibility that the closure types might be kept distinct, but the issue clearly needs more discussion than I gave it. (Note, by the way, that it would not make sense to propose, and I am not proposing, that closures and pointers to *member* functions should become the same thing. -- The previous sentence demonstrates what a mess some of this terminology has become! "Pointers to members" not only aren't pointers, but they aren't the same things as pointers that point to members! "Member pointers" would be a slightly better term; as someone else has pointed out (so to speak), "member offsets" would be even better. So, let me say that again: I am not proposing that closures and member function offsets become the same thing; that would not make sense.) The issue you raise comes up only when one tries to mix C-compiled code and C++-compiled code in the same program. Even if pointers to functions became closures, a conforming ANSI C program which was entirely compiled by a C++ compiler would still work (at least in this particular respect); all that would happen is that the objects it declared as pointers to functions would get larger. However, in the mixed compilation case, the problems do, admittedly, get fairly nasty, because pointers to functions wind up with different sizes in C and C++. I don't suppose C compiler vendors would be real enthusiastic about provide "C++ compatibility mode", either. So even though, from the point of view of elegance, I would much prefer that pointers to functions become closures, this is a pretty strong argument for keeping them distinct. >If the concepts are indeed kept distinct, a clearly distint syntax is >necessary. My suggestion would be to use a unary && (an extention of >taking the address of a function). This operator would also be used >in the declaration. Forgive me, but: yuck. It seems to me that no distinct syntax is required for closure creation or invocation. We simply 1) say that the value of a member function, with or without the `&', is a closure, and 2) define a built-in conversion from function pointers to closures (but not the other way!). Then you write the same thing in either case, and which one you get depends properly on context. Since there's no automatic conversion from closures to function pointers, one of the four cases is an error, but the other three work fine. That leaves us with the question of declaration syntax. Hmm... Well, even though I think it's ugly, there is precedent for the use of the `&' in place of the `*' in a declarator to declare "something sortof like a pointer". (Though I wouldn't want to use `&&'.) Geez, I'm really not too crazy about this but I can't think of anything better offhand. That leaves us with: class Foo { public: int f(); }; int g(); bar() { int (&cl)(), (*pf)(); Foo foo; pf = g; // okay pf = &g; // likewise for this form cl = g; // also okay cl = foo.f; // also okay pf = foo.f; // error! pf = (int (*)()) foo.f; // probably legal, but will crash when pf is invoked } >If you don't want to take me seriously, you don't have to; I'm only an >undergraduate with an enthusiasm for C++. Oh, don't worry, even if you weren't an undergraduate, I could still decline to take you seriously. -- Scott Gyro@Reasoning.COM
wmm@world.std.com (William M Miller) (04/29/91)
gyro@kestrel.edu (Scott Layson Burson) writes: > That leaves us with the question of declaration syntax. Hmm... Well, > even though I think it's ugly, there is precedent for the use of the > `&' in place of the `*' in a declarator to declare "something sortof > like a pointer". (Though I wouldn't want to use `&&'.) Geez, I'm > really not too crazy about this but I can't think of anything better > offhand. That leaves us with: >... > int (&cl)(), (*pf)(); Sorry, "int (&cl)()" already means something: a reference to a function (yes, it is legal). You'll have to keep looking for a closure syntax. -- William M. Miller, Glockenspiel, Ltd. wmm@world.std.com
gyro@kestrel.edu (Scott Layson Burson) (05/01/91)
In article <1991Apr28.231328.2064@world.std.com> wmm@world.std.com (William M Miller) writes: >Sorry, "int (&cl)()" already means something: a reference to a function >(yes, it is legal). You'll have to keep looking for a closure syntax. A reference to a function? What on earth does it mean??? But it doesn't matter -- I've been chatting with Jerry Schwartz about this, and it turns out he has already made a suggestion along rather different lines (in document X3J16/91-0011), which is to treat `extern "C"' as though it were part of the type of the function or function pointer; so there would be a distinction between extern "C" void (*f)(); and void (*f)(); I like this a lot: the ugliness of the solution is exactly symmetrical with the ugliness of the problem, which to me is the definition of an elegant solution. Besides, this approach casts the change in the correct light, I think: we're not really proposing a new construct "closures", we're just fixing pointers to functions so they work right for member functions. -- Scott Gyro@Reasoning.COM
horstman@mathcs.sjsu.edu (Cay Horstmann) (05/10/91)
In article <1991Apr30.201532.13706@kestrel.edu> gyro@kestrel.edu (Scott Layson Burson) writes: > >But it doesn't matter -- I've been chatting with Jerry Schwartz about >this, and it turns out he has already made a suggestion along rather >different lines (in document X3J16/91-0011), which is to treat `extern >"C"' as though it were part of the type of the function or function >pointer; so there would be a distinction between > > extern "C" void (*f)(); > >and > > void (*f)(); > >I like this a lot: the ugliness of the solution is exactly symmetrical >with the ugliness of the problem, which to me is the definition of an >elegant solution. Good. This is pretty important stuff on DOS machines (and probably on others) that use a "Pascal" calling convention for C++ functions and a C calling convention for (you guessed it) C functions. And it really shows the bankrupcy of the C style declaration syntax that jumbles up - the name of the object to be declared - its type - its storage class in a hopeless way. Just out of curiosity: Suppose I have a variable f that holds a pointer to a "C" style void->void function and I want to make f local to its source file, do I say static extern "C" void (*f)(); Cay
bs@chaos.cs.brandeis.edu (Benjamin Schreiber) (05/13/91)
In article <1991May10.003304.12390@mathcs.sjsu.edu> horstman@mathcs.sjsu.edu (Cay Horstmann) writes: >Just out of curiosity: Suppose I have a variable f that holds a pointer >to a "C" style void->void function and I want to make f local to its source >file, do I say > > static extern "C" void (*f)(); > >Cay What you proposed is not syntacticly valid. From reading the grammar, I get the feeling that the correct way to do this is: extern "C" { static void (*f)(); }; Since any declarations/definitions are gramaticly allowed inside an extern construct. The following may also be valid: extern "C" static void (*f)(); I don't know whether current implementations support this (I have nothing to test this on), but they should. Another use for this flexibility might be to provide a way to define resources for the Macintosh within the C++ source (i.e. extern "resource ICON,128" {....}). Just a thought. (Note: I am refering to AT&T 2.1, not ARM) -- Ben Schreiber bs@cs.brandeis.edu
comeau@ditka.Chicago.COM (Greg Comeau) (05/14/91)
In article <1991May12.194247.21483@news.cs.brandeis.edu> bs@chaos.cs.brandeis.edu (Benjamin Schreiber) writes: >In article <1991May10.003304.12390@mathcs.sjsu.edu> horstman@mathcs.sjsu.edu (Cay Horstmann) writes: >>Just out of curiosity: Suppose I have a variable f that holds a pointer >>to a "C" style void->void function and I want to make f local to its source >>file, do I say static extern "C" void (*f)(); No, that is a declaration with 2 storage class specifiers that differ in their linkage demands. >From reading the grammar, I get the feeling that the correct way to do this >is: extern "C" { static void (*f)(); }; It is. >Since any declarations/definitions are gramaticly allowed inside an extern >construct. The following may also be valid: extern "C" static void (*f)(); It is not. That still contained 2 storage class specifiers with different linkage. Getting back to the original data type though. This: extern "C" { static void (*f)(); }; is not "a variable f that holds a pointer to a "C" style void->void function". Instead it is f that is being extern C'd. The correct decl for the original problem would be: extern "C" typedef void (*ptCf)(); [static] ptCf f; This typedef ugliness must be used. I honestly don't know how many compilers accept this though. - Greg -- Comeau Computing, 91-34 120th Street, Richmond Hill, NY, 11418 Producers of Comeau C++ 2.1 Here:attmail.com!csanta!comeau / BIX:comeau / CIS:72331,3421 Voice:718-945-0009 / Fax:718-441-2310