jar@io.UUCP (Jim Roskind x3266) (06/14/89)
Warning: This is my first posting, and I am new to C++. Have mercy on me if I under-, over-, or mis-state, or mis-route. I don't have the message numbers, but... Ron Guilmette wrote: >>How can you declare a function which can accept a pointer >>to itself as an argument (in a type-safe manner of course)? Andrew Koenig replied: > You can't in C++ or C. It is interesting to note that you can get much closer than expected to this goal using ANSI C (and you can get (for all practical purposes) EXACTLY the desired result using C++). Koenig is correct, but, the following can be achieved in C++: typedef int return_type; /* cute stuff goes here, complete code given below. The result is a new pointer type called P, which "acts" like a "pointer to a function", with a protypical argument P. */ // Typical monadic function definition return_type function(P p_to_function) { p_to_function = & function; // support assignment to pointer p_to_function = function; // equivalent form under ANSI C // or C++ // support comparison ==, != p_to_function == function; function == p_to_function; p_to_function == p_to_function; // Support all forms of function call function (function); p_to_function (function); function (p_to_function); p_to_function (p_to_function); return 0; } The reason why Koenig probably said it was impossible is because it would require a self-referant type, which is usually not supported by C. The trick to exploit is the fact that self-referant type definitions are allowed using struct/union/class. The following example provides the desired C definition. It should be appreciated that the code generated when using the construct given below will be exactly identical to the user's "desired type" (pointer to function). At least on most machines, a pointer is as large as the smallest size structure/union (structures usually have significant minimal size due to alignment restrictions). Since pointers are generally fairly large, no memory is wasted. Most compilers are also smart enough to notice that accessing the first element (in this case, only element) of a structure uses a zero offset. Hence NO effort is expended in object code to support this completely type-safe source code construction. typedef int any_type; union P { any_type (* point)(struct P )} ; Now P is EFFECTIVELY a pointer to a function. The gotcha of course is that the member operator ('.' or '->') must be used to make a call to such a function. (It is cute to notice that a union (or a class/struct) with a single element unambiguously refers to its lone member, given only identifier name. I don't know of any C implementations that currently support this. I wonder if "anonymous union" sort of tricks would work here??) Typical (?) C use is then: any_type function(union P p_to_function) { p_to_function.point(p_to_function); } Having seen the above code, it is straight forward to see how C++ allows us to reach our desired goal completely: cut here --------------------------- // Except for 2 lines, Zortech compiles this with no problem. */ #include <stream.hpp> typedef int return_type; class P { return_type (*point)(P); // Simple self reference P(return_type (*real_pointer)(P)) {point=real_pointer;} return_type operator()( P argument) {return point(argument);} friend int operator==( P left, P right){return left.point==right.point;} friend int operator!=( P left, P right){return !(right == left);} }; // Typical monadic function return_type function(P p_to_function) { static stupid_counter = 100; if (0 > stupid_counter) return 0; // recursion terminator stupid_counter--; p_to_function=function; // support assignment //Zortech v1.07 doesn't like the next 2 lines, but '&function' could be // used to satisfy it. I believe the code is correct however. p_to_function == function; // support comparison ==, (!= also works) function == p_to_function; p_to_function == p_to_function; // Zortech likes all the following lines (and so do I). function(function); // type-safe argument of itself // although there is an arg conversion p_to_function(function); // Overloaded operator() after arg conversion function(p_to_function); // no conversions needed p_to_function(p_to_function); // Simple overloaded operator() return 0; } -------------- end cut ...and all is type safe. No casts, no slight of hand. In a correspondence with Koenig on this topic, he indicated that he now recalls seeing code of this sort, and he refers to such classes as "functionoids". (Classes that act like functions). Although the class definition certainly leads to the name functionoids, the underlying concept here appears to me to be that of "smart pointers". Smart pointers (at least as far as I use the term) are classes that are used syntactically as though they were pointers, but their use typically causes some additional background activity such as null pointer detection, dynamic type verification, or memory management. In this case the background activity is the smooth cast-free motion through a self-referant type. As an aside, the issue of passing functions has come up in my C code in the past. (So I certainly don't think this is a silly question). I enjoy seeing the completeness of the resolution under C++. Function prototyping and enhanced type checking are some of the GREATEST things to happen to C. In a real life application, where I only had to write the above code once, and then everyone could use the typedef-name (and include file), I would probably use the above C++ code. For a quick and dirty solution under C, I would probably leave the the second generation self-referant prototype as "()", and allow the ANSI C compiler to "not complain" during the assignment of "compatible" but not identical types. This is actually a hack that exploits the pre-prototype type system (in some sense, either an "implicit" cast, or an official exemption from the type check system). Example: typedef return_type (*p2_func)(); typedef return_type (*p_func)(p2_func); The above two types are compatible under ANSI C. The latter is well enough defined that an identifier of that type can be used to direct a function call (without requiring a cast of the argument). Under C++, these types are not assignment compatible (the p2_func is defined to have a void arg list in C++, and putting in "..." would not help matters). Under C++, I am forced to either use the fully type safe code given in this note, or cast (hiss, boo, hiss: abandon the safety of type checking). Two of my favorite rules in C programming are: 1) NEVER use cast operators. They obscure errors. 2) Be sure you have a good reason when you do use a cast. Seems like with C++, there is just one less reason to cast. Jim Roskind Independent Consultant (Contracting with Interleaf, Cambridge MA) 516 Latania Palm Drive Indialantic Florida, 32903-3816 (407)729-4348 Current Email address: ...!mit-eddie!ileaf!jar