Keyword parameters

J

James Harris

Malcolm McLean said:
You can't use strings for speed-critical code, agreed.
C programmers do expect runtime micro-efficiency. But you're already being
non-
idiomatic by introducing named arguments.

Is it being non-ideomatic or is it just using varargs (which could be called
idiomatic due to heavy usage in printf-family function calls) sensibly? Or
is it non-ideomatic for using varargs in an unusual way?

Normal uses of varargs (if they could be called normal) use one parameter to
control what the rest do. For example, sprintf has the form

sprintf(stream, string, ...)

In this case the string defines the number and types of arguments expected.
But if we wanted a call similar to malloc to which we could pass some other
parameters it might be better to use

mem_alloc(size, ...);

In this case the parameters could appear as pairs of (attribute_id, value)
followed by a final pseudo attribute id of zero. So the basic call without
any extra parms would be

mem_alloc(size, 0);

A call to allocate memory below a certain address might be

mem_alloc(size, MEM_BELOW, addr_limit, 0);

where MEM_BELOW is the parm id integer used by the mem_* routines.
That's largely bad.

You can just use "x" or "N" as a string. With integers and bit masks, it's
got to be
QLIB_CURSOR_X.

Yes, the lengths of the names and, in particular, their prefix, would be an
issue due to C's lack of namespace support. (One of the few things that
can't be fixed by low-level programming as it's a limitation of the
compilation.)
That's a slight advantage. You can't pass "X" or "x " accidentally. But
mostly those bugs
are easily caught when you parse the name / argument pairs.

Well, YMMV but to me that's a big advantage.

James
 
M

Malcolm McLean

* You need to use the mechanisms for varargs, and specially designed
function interfaces (you can't apply directly to existing functions)
You can write a function which takes twenty or thirty positional arguments,
then write a varargs interface for it.
* There are overheads with processing the list of pairs, and assembling the
parameters properly.
Yes.

* There is no compile-time checking of parameter names and argument types,
or whether you've passed the same parameter twice for example.
Yes. But runtime checks are easy, and will catch most bugs early.
* You can't mix positional and keyword parameters (except I think by
specifying a fixed number of positional parameters, although that might
depend on how varargs works, and the order they are pushed).
You can specify that the first few arguments are mandatory, and positional,
then use the varargs mechanism for the remaining optional arguments.
I wouldn't recommend that, as it's likely to be confusing.
 
J

James Harris

BartC said:
Well, when they added designated initialisers to the language, they could
have thrown in keyword parameters too, as it's a similar sort of thing. I
think the latter would be more useful.


For C it's good.

If it's good for C the it's good enough! :) In fact, as with the exception
object discussion this approach would work with other languages than just C.
As long as a varargs-type mechanism is available the approach should work
because each pair (id, value) is just two normal parameters.

It could also work between languages. Practically, I could use this to pass
keyword args between C functions and/or between C and assembly functions.

I'm glad both that someone suggested it and that others haven't pounced on
it to say how terrible it is! That means at least that it doesn't send
feelings of revulsion through the C community. ;-)
I've used the same approach at some time or other (often
when needing to provide some options out of a large selection, not
necessarily when using parameters).

The disadvantages compared with proper keyword parameters:

* You need to use the mechanisms for varargs, and specially designed
function interfaces (you can't apply directly to existing functions)

* There are overheads with processing the list of pairs, and assembling
the
parameters properly.

* There is no compile-time checking of parameter names and argument types,
or whether you've passed the same parameter twice for example.

Yes, I agree with all those points.
* You can't mix positional and keyword parameters (except I think by
specifying a fixed number of positional parameters, although that might
depend on how varargs works, and the order they are pushed).

Postional and keyword args could be included in one function call. The
positionals would have to come first. Varargs supports that. I believe the
iteration over parameters by the called routine is kicked off using va_start
and referring to the last positional parameter.

Actually, that raises an issue. Presumably to use varargs there has to be at
least one positional parameter. If that's right that's something to bear in
mind: no all-keyword parameter lists as I assumed would be possible in my
example, above.
(Keyword parameters can be applied any function, including existing
library
functions (they just need named parameters, but that can be arranged);
there
are no extra overheads involved; there would be compile-time checking; and
can have positional parameters as well. There would usually also be a
mechanism to specify a default value.)

I cannot see how exising library functions could have keyword parms added.
At least not without breaking their interfaces.

James
 
M

Malcolm McLean

Actually, that raises an issue. Presumably to use varargs there has to be at
least one positional parameter. If that's right that's something to bear in
mind: no all-keyword parameter lists as I assumed would be possible in my
example, above.
If you call a function with no parameters, you need to call

function("");

or whatever you use as the end sentinel.

But you can happily call

function("optionX", 100, "");

and so on. It's just a little bit fiddly because the parser has to accept the first argument as a
char *, the rest as variable arguments.
 
B

BartC

I cannot see how exising library functions could have keyword parms added.
At least not without breaking their interfaces.

Bearing in mind that this applies to a fantasy version of C which has
keyword parameters and therefore could have arrangements to make this
possible:

This is an example call to a Windows library function:

#include <stdio.h>
#include <windows.h>

int main(void) {
MessageBoxA(0,"Hello","Caption",0);
}

To make keyword parameters possible, then MessageBoxA needs to have named
parameters, but the default declaration only provides types. However C
allows the declaration to be duplicated, this time with names:

#include <windows.h>

WINUSERAPI int WINAPI MessageBoxA(HWND handle,LPCSTR message,LPCSTR
caption,UINT flags);

Now the call could become [not currently possible in C]:

MessageBoxA(caption="Caption",message="Hello");

Default values can also be specified [not currently possible in C]:

WINUSERAPI int WINAPI MessageBoxA(HWND handle=0,LPCSTR message,LPCSTR
caption,UINT flags=0);

So we can directly call a function, that's been established for 20 years
with fixed parameters, using optional and keyword parameters; the interface
hasn't changed. The mechanism for dealing with this will simply call it with
the parameters it expects:

MessageBoxA(0,"Hello","Caption",0);
 
B

BartC

This is an example call to a Windows library function:
MessageBoxA(0,"Hello","Caption",0);

BTW here is an actual working example from outside of C. This is a little
easier in that all such APIs need to be rewritten anyway:

windows function MessageBoxA(int hwnd=0, ref char message="Error",
caption="Caption", int flags=0)int

MessageBoxA(message:"This is not C!")

This requires that all parameters that can be omitted be given default
values (also I need to use ":" instead of "=" as the latter has another
meaning; probably C will have the same problem).

While this is not C, it ends up calling exactly the same function, with the
same established API, that C is striving to call.
 
K

Keith Thompson

BartC said:
I cannot see how exising library functions could have keyword parms added.
At least not without breaking their interfaces.

Bearing in mind that this applies to a fantasy version of C which has
keyword parameters and therefore could have arrangements to make this
possible:

This is an example call to a Windows library function:

#include <stdio.h>
#include <windows.h>

int main(void) {
MessageBoxA(0,"Hello","Caption",0);
}

To make keyword parameters possible, then MessageBoxA needs to have named
parameters, but the default declaration only provides types. However C
allows the declaration to be duplicated, this time with names:

#include <windows.h>

WINUSERAPI int WINAPI MessageBoxA(HWND handle,LPCSTR message,LPCSTR
caption,UINT flags);

Now the call could become [not currently possible in C]:

MessageBoxA(caption="Caption",message="Hello");
[...]

You'd need to invent a different syntax, since

caption="Caption"

is already a valid expression.

(Ada uses "=>" for this; if C were to add keyword arguments, that might
be a reasonable choice.)
 
S

Stefan Ram

James Harris said:
I don't think I understand that. Do you mean that the keyword parameters
would be stored in globals?

For simplification, I have not written:

keyword0( object_pointer, kparm0 );
keyword1( object_pointer, kparm0 );
f( object_pointer, pos0, pos1 );
 
P

Phil Carmody

Keith Thompson said:
BartC said:
....
MessageBoxA(caption="Caption",message="Hello");
[...]

You'd need to invent a different syntax, since

caption="Caption"

is already a valid expression.

(Ada uses "=>" for this; if C were to add keyword arguments, that might
be a reasonable choice.)

I was about to post some Perl, and see if anyone noticed!
(It also uses '=>' for positional parameters, but deep down
they're no more than syntactic sugar for commas.)

Phil
 
A

ais523

Phil said:
Keith Thompson said:
MessageBoxA(caption="Caption",message="Hello");
[...]

You'd need to invent a different syntax, since

caption="Caption"

is already a valid expression.

(Ada uses "=>" for this; if C were to add keyword arguments, that might
be a reasonable choice.)

I was about to post some Perl, and see if anyone noticed!
(It also uses '=>' for positional parameters, but deep down
they're no more than syntactic sugar for commas.)

The Perl syntax would be

MessageBoxA({caption => "Caption", message => "Hello"});

It strikes me that something very similar can be done in C99:

MessageBoxA((struct MessageBoxA_params)
{.caption = "Caption", .message = "Hello"});

although this only works if the values for absent parameters are NULL or
0.
 
N

Noob

NULL should be cast to whatever pointer type the function expects.

I disagree.

1) Since we are dealing with a variadic function, it is NOT obvious
"whatever pointer type the function expects".

2) NULL will just be used as a sentinel 86.37% of the times.

3) (void *) is compatible with other object pointers anyway.

Are you suggesting API users should read the function's implementation,
just so they can provide what they think the function expects?

IMO, a good rule is a simple rule.

RULE #42 OF THE C FIGHT CLUB:

When passing NULL to a variadic function, always pass (void *)NULL

Regards.
 
B

Ben Bacarisse

Noob said:
3) (void *) is compatible with other object pointers anyway.

I think you mean that void * can be converted to other object pointer
types. The term "compatible" has a well-defined technical meaning, and
the type void * is not compatible with other pointer types.

<snip>
 
J

James Kuyper

I disagree.

1) Since we are dealing with a variadic function, it is NOT obvious
"whatever pointer type the function expects".

If you don't know what pointer type the function expects, you can't use
the function safely. 7.16.1.1p2 describes the va_arg() macro, and says
"... if _type_ is not compatible with the type of the actual next
argument (as promoted according to the default argument promotions), the
behavior is undefined, except for the following
cases:
....
— one type is pointer to void and the other is a pointer to a character
type."

"int*", for instance, unchanged by the default argument promotions, is
neither a pointer to a character type, and is not compatible with
"void*", so passing a "void*" pointer to a variadic function that is
expecting an "int*" would have undefined behavior.
2) NULL will just be used as a sentinel 86.37% of the times.

3) (void *) is compatible with other object pointers anyway.

"For two pointer types to be compatible, both shall be identically
qualified and both shall be pointers to compatible types." (6.7.6.1p2)
"void*" is not, for instance, compatible with "int*". It's not even
compatible with "const void*", since they are not identically qualified.

You're thinking about the fact that pointers to arbitrary object types
can be safely converted to "void*" and back again - but "convertible
types" are not the same as "compatible types".
Are you suggesting API users should read the function's implementation,
just so they can provide what they think the function expects?

No - they should read the function's documentation, which should tell
them what the expected type is. The documentation for fprintf(), for
example, explains that "%p" expects a (void*).
IMO, a good rule is a simple rule.

RULE #42 OF THE C FIGHT CLUB:

When passing NULL to a variadic function, always pass (void *)NULL

Try doing that with fscanf("%lf"). The documentation for fscanf() says
"The corresponding argument shall be a pointer to floating."
(7.21.6.2p12). "If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears
outside of a constraint or runtime-constraint
is violated, the behavior is undefined." (4p2). 7.21.6.2 is not a
constraint nor a runtime-constraint.
 
I

Ike Naar

I disagree.

1) Since we are dealing with a variadic function, it is NOT obvious
"whatever pointer type the function expects".

It is known that the variable part of the argument list has the form

"keyword0", param0, "keyword1", param1, ... , "keywordN", paramN, SENTINEL

where all "keyword*" entries have type char*, so it would not be
unreasonable to let the sentinel have type char* as well since that
is the type that the function expects for all odd-numbered entries.
 
N

Noob

Try doing that with fscanf("%lf"). The documentation for fscanf() says
"The corresponding argument shall be a pointer to floating."

Are you suggesting that the following statement is "bad":

scanf("%lf", (void *)NULL);

while the following statement is not?

scanf("%lf", (double *)NULL);

AFAICT, scanf expects a /valid/ pointer.

Regards.
 
J

James Kuyper

Are you suggesting that the following statement is "bad":

scanf("%lf", (void *)NULL);

while the following statement is not?

scanf("%lf", (double *)NULL);

AFAICT, scanf expects a /valid/ pointer.

Sorry - I was paying too much attention to the issue of the pointer
type, and not enough attention to the pointer value. However, if a
variadic function has defined behavior when passed a null pointer to a
non-character non-void type (I don't think that there's any such
function in the C standard library), you should not pass (void*)NULL for
that argument.
 
N

Noob

Sorry - I was paying too much attention to the issue of the pointer
type, and not enough attention to the pointer value. However, if a
variadic function has defined behavior when passed a null pointer to a
non-character non-void type (I don't think that there's any such
function in the C standard library), you should not pass (void*)NULL for
that argument.

OK, this is the part I didn't understand well.

Consider this (bogus) variadic function:

#include <stdarg.h>
int foob(int u, ...)
{
int res;
va_list ap;
va_start(ap, u);
double *p = va_arg(ap, double *);
res = p ? (*p > 0) : 42;
va_end(ap);
return res;
}

If I understand correctly what you wrote in your previous post,
then calling

foob(42, (void *)0);

has undefined behavior, because the caller passes a (void *) argument,
but the function expected a (double *) through va_arg?

Regards.
 
R

Richard Tobin

James Kuyper said:
However, if a
variadic function has defined behavior when passed a null pointer to a
non-character non-void type (I don't think that there's any such
function in the C standard library)

The second argument to strtod() is such.

-- Richard
 
K

Keith Thompson

Noob said:
I disagree.

1) Since we are dealing with a variadic function, it is NOT obvious
"whatever pointer type the function expects".

It's obvious from the function's documentation. If it's not obvious,
the function cannot be used safely.
2) NULL will just be used as a sentinel 86.37% of the times.

NULL is very often of type int. That number is obviously bogus (I
presume intentionally so), but even if it were 99.99%, I don't like to
write code that works *most* of the time. Program correctness isn't a
matter of probabilities.
3) (void *) is compatible with other object pointers anyway.

No, it's not. Type compatibility is a more stringent condition than
whatever you're thinking of; see the standard for its definition. Types
that can be converted to each other, either implicitly or explicitly,
are not necessarily compatible. Even types with the same representation
aren't necessarily compatible.

Are you suggesting API users should read the function's implementation,
just so they can provide what they think the function expects?

No, I'm suggesting they should read the function's documentation.
IMO, a good rule is a simple rule.

RULE #42 OF THE C FIGHT CLUB:

When passing NULL to a variadic function, always pass (void *)NULL

No, RTFM and pass the correct type.

For example, the POSIX execl() function is declared as:

int execl(const char *path, const char *arg0, ... /*, (char *)0 */);

Now it happens that char* and void* are required to have the same
representation, but since we know the arguments are of type char*,
there's no good reason not to pass arguments of type char*.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,733
Messages
2,569,440
Members
44,830
Latest member
ZADIva7383

Latest Threads

Top