[net.lang.c] macro to specify output parameter

ht01@bunny.UUCP (Chen Tu) (08/14/86)

    When I read C functions, many times I was confused about the roles
    played by their parameters:  it is hard to tell a pointer
    parameter is an array or a pointer to a scaler (to be used as an
    output parameter).  To distinguish an output parameter from an
    array parameter, where both use the '*' notation, I found it is
    useful to have a macro to replace the notation of the former.

	    #define OUT(name) *name
	     
	    /*** output parameter: ***/
	    foo(c)
	    char OUT(c);   <== output parameter, point to a char.
	    {       ...
		    OUT(c) = 'a';
		    ...
		    }
	     
	    /*** array parameter: ***/
	    foo(s)
	    char *s;      <== input parameter, a character string.
	    {
		    ... *s++ ... }
	     
    To make the code more readable, do not use the output parameters
    in other places, and only use OUT(name) on the lhs of assignments, i.e.,
    treat them as OUTPUT ONLY parameters.

    When the output parameter is also a pointer, we have
	    foo(s) char * OUT(s); { ...; OUT(s) = "this is a test"; ...}
    compare to
	    foo(s) char **s; { ...; *s = "this is a test"; ...}
    The former seems more readable and understandable.

    Another alternative style is: (I have no preference)

	    #define OUT *
	    foo(c) char OUT c; { ...; OUT c = 'a'; ...}

				Hai-Chen Tu
				GTE Laboratories
				Waltham, Mass 02254
				(617) 466-4124
				CSNET: ht01%gte-labs.csnet

guy@sun.uucp (Guy Harris) (08/16/86)

>     When I read C functions, many times I was confused about the roles
>     played by their parameters:  it is hard to tell a pointer
>     parameter is an array or a pointer to a scaler (to be used as an
>     output parameter).

Well, actually, pointer parameters are never arrays; arrays and pointers are
two completely different things.  This may be a good argument for allowing
"&" to be applied to arrays (yielding a pointer value of type "pointer to
array of <whatever>", NOT type "pointer to <whatever>"!) and using "pointer
to array" as the argument type if you really want to pass a pointer to the
array.

Of course, in the second example routine "foo", it may not really be a
pointer to the first element of an array/string; that routine might be used
to stuff characters into some arbitrary place in a string, so it's not
really a pointer to a string/array, it's just a pointer to a "char" that is
assumed to be somewhere within a string/array.

This may, in fact, also be an argument for reference types a la C++; there,
your routine "foo" would be written as

	foo(c)
		char &c;
	{
		...
		c = 'a';	/* store 'a' in the "char" referred to by
				   "c" */
		...
	}

This is how call-by-reference is done in C++; in other languages that
implement call-by-reference, output parameters are generally passed by
reference.

Your second proposed style comes close to implementing this as best as can
be done in C.  Try

	#define	REF	*	/* for defining reference types */
	#define	DEREF	*	/* dereferencing operator; since C doesn't
				   know about reference types, it won't
				   automatically dereference them */

	foo(c)
		char REF c;
	{
		...
		DEREF c = 'a';
		...
	}
-- 
	Guy Harris
	{ihnp4, decvax, seismo, decwrl, ...}!sun!guy
	guy@sun.com (or guy@sun.arpa)

stuart@BMS-AT.UUCP (Stuart D. Gathman) (08/20/86)

In article <6229@sun.uucp>, guy@sun.uucp (Guy Harris) writes:
> This may, in fact, also be an argument for reference types a la C++; there,
> your routine "foo" would be written as
> 
> 	foo(c)
> 		char &c;
> 	{
> 		...
> 		c = 'a';	/* store 'a' in the "char" referred to by
> 				   "c" */
> 		...
> 	}

I don't like this.  It violates the nice consistent way that C expressions
work.  'char *c' means that '*c' is of type char.  '&c' is not of type
char in any other context.  I don't see any problem with using '*' instead
of REF and DEREF.  In any case, either '*' or 'DEREF' is better than
the foo example above since the uses of the output parameter are marked.
This is certainly an argument for DEREF over '*', but I still think
'*' looks prettier (but then I love APL).

I would like to see variable size arrays allowed as local parameters.
This would be a lot more efficient than using malloc().

I would like to "operator definition" which would allow you to opdef
say '+' to cause it and its two arguments to be replaced by a macro
called with the two arguments whenever the arguments are of a type
specified in the definition.  This allows generalized treatment of
operators on non-native types such as 'complex'.  Perhaps new operators
could be custom defined or the syntax (left/right association, priority)
of existing operators changed (but probably not except for completeness,
changing existing operators is a good way to make a program unreadable).

I would like to see "structure constants" which would allow assignment
to aggregate types.

	typedef struct { double real, imag; } complex;
	complex z;
	int a;
	a = 0;
	z = { 0, 0 };
	. . .
	z = { a+1, exp(b) };
	foo( (complex) { a, b } );

Of course any of these could be coded by assigning each member in turn.
I just think the above is nicer and not hard to implement.

Macros would be nicer with "imbedded functions".

#define	foo(x,y) { float a = 0.0; while (x) { a += y * x--; }; return a; }

This construct is distinguished by the use of '{}' in a context
requiring a value.  The return is optional, the value of the last
expression is used.  Perhaps loops should be allowed in comma expressions
instead.

guy@sun.uucp (Guy Harris) (08/21/86)

> > 	foo(c)
> > 		char &c;
> > 	{
> > 		...
> > 		c = 'a';	/* store 'a' in the "char" referred to by
> > 				   "c" */

> I don't like this.  It violates the nice consistent way that C expressions
> work.  'char *c' means that '*c' is of type char.  '&c' is not of type
> char in any other context.

Well, maybe a keyword like "ref" would have been preferable.  However, the
"syntax of declaration resembles syntax of use" rule of C may be consistent,
but also can be confusing, so I don't consider it particularly holy.

Also note that "&c" is not of type "char" in ANY context; inside the body of
"foo", "&c" is of type "pointer to char", since it takes the address of the
"char" referred to by "c".  (The code generated for that would almost
certainly just take the value of the pointer that implements "c" and use
it.)  Dereferencing of reference types is automatic, so "syntax of
declaration resembles syntax of use" doesn't apply here.

> I don't see any problem with using '*' instead of REF and DEREF.

Since reference types and pointer types have different semantics, there are
cases, at least in C++, where reference types are, I believe, necessary.
Also, there may be cases where writing code using reference types could
eliminated the possibility of aliasing and permit better optimization.

Also, pointer arguments are overloaded in C; they are used if you actually
want to pass a pointer to a routine (because the routine will actually be
modifying the pointer value, e.g. "strcpy", where the pointer merely
indicates the address of the *first* character to copy to and the *first*
location to copy it into), and also if you want to do "call-by-reference".
The presence of reference types makes it possible to state your intent more
directly in C++ than in C.

> In any case, either '*' or 'DEREF' is better than the foo example above
> since the uses of the output parameter are marked.

It's not clear that marking the *uses* is interesting.  Is one just trying
to find the places where the item pointed to by the argument is modified?
You might want to do that with objects other than formal parameters, so
using "*" or "DEREF" doesn't solve the entire problem.

One might want to mark the *definition* instead; if the object referred to
by the parameter is read-only, one obviously doesn't have to look for the
places it's modified.  This can be done with the "const" type modifier in
ANSI C and C++, both for pointer and reference types.  If the routine takes
a reference or pointer to something and *doesn't* modify that something, it
should be declared as taking "pointer to 'const' whatever" or "reference to
'const' whatever"; the compiler will refuse to compile any code that
attempts to modify that something.

> I would like to see variable size arrays allowed as local parameters.
> This would be a lot more efficient than using malloc().

I presume you mean "local variables", and not "local parameters"; you can
already handle variable-sized array parameters by the subterfuge of passing
a pointer to the array (or to its first member) along with its length (if
you *don't* pass the length, make sure you can't run off the end!).
Supporting variable-sized arrays, even just as local variables, adds some
complication to the compiler.  Consider a function with two such arrays.
The address of the second such array (or the first, depending on the order
in which they're allocated), is a variable.  This would most likely be
supported by computing the address at the time it's allocated, stuffing it
into an invisible local variable, and replacing references to the array with
dereferences of the pointer.

If you support them in structures, the complications get worse.

> I would like to "operator definition" which would allow you to opdef
> say '+' to cause it and its two arguments to be replaced by a macro
> called with the two arguments whenever the arguments are of a type
> specified in the definition.  This allows generalized treatment of
> operators on non-native types such as 'complex'.

Check out C++; you can do exactly that there.  You can overload existing C++
operators, binding them to functions.  You can then declare the function to
be "inline" so that the code is expanded in-line rather than producing a
function call.  In fact, one example Stroustrup gives is the addition of the
type "complex" to C++, without changing the compiler and without turning all
complex arithmetic operations into procedure calls.

> Perhaps new operators could be custom defined or the syntax (left/right
> association, priority) of existing operators changed (but probably not
> except for completeness, changing existing operators is a good way to
> make a program unreadable).

Probably not even for completeness; the added cost of complication in the
compiler doesn't seem worth it.  (Isn't there a minor industry producing
discussions of implementation of overloading in Ada compilers?)

> I would like to see "structure constants" which would allow assignment
> to aggregate types.

C++ has this, using "constructors".  Any "class", and structures are special
cases of classes, can have a "constructor" as a member function.  A
"constructor" can take arbitrary argument lists, and merely need construct
an object of the appropriate type.

I think if you declare the "constructor" as "inline", it will do the "right"
thing (i.e.,

	typedef class complex {
		double	real;
		double	imaginary;
	public:
		inline complex(double r, double i = 0) {
			real = r;
			imaginary = i;
		}
		...
	} complex;

will cause

	complex i = complex(1, 0);

to generate code like

	i.real = 0;
	i.imaginary = 1;

I don't know whether it can be set up to do this at compile time for static
objects.

> Macros would be nicer with "imbedded functions".
> 
> #define	foo(x,y) { float a = 0.0; while (x) { a += y * x--; }; \
			   return a; }

See David Chase's recent posting; he proposes more-or-less the same sort of
thing, mentioning the similar sort of construct in BCPL.

Of course, for this sort of thing, C++ "inline" functions might be better.

Give a look at C++; don't let its syntax for reference types put you off.
-- 
	Guy Harris
	{ihnp4, decvax, seismo, decwrl, ...}!sun!guy
	guy@sun.com (or guy@sun.arpa)