Keyword parameters

Discussion in 'C Programming' started by James Harris, Jun 1, 2014.

  1. James Harris

    James Harris Guest

    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.
    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.)
    Well, YMMV but to me that's a big advantage.

    James
     
    James Harris, Jun 2, 2014
    #21
    1. Advertisements

  2. You can write a function which takes twenty or thirty positional arguments,
    then write a varargs interface for it.
    Yes. But runtime checks are easy, and will catch most bugs early.
    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.
     
    Malcolm McLean, Jun 2, 2014
    #22
    1. Advertisements

  3. James Harris

    James Harris Guest

    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. ;-)
    Yes, I agree with all those points.
    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.
    I cannot see how exising library functions could have keyword parms added.
    At least not without breaking their interfaces.

    James
     
    James Harris, Jun 2, 2014
    #23
  4. 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.
     
    Malcolm McLean, Jun 2, 2014
    #24
  5. James Harris

    BartC Guest

    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);
     
    BartC, Jun 2, 2014
    #25
  6. James Harris

    BartC Guest

    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.
     
    BartC, Jun 2, 2014
    #26
  7. [...]

    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.)
     
    Keith Thompson, Jun 2, 2014
    #27
  8. James Harris

    Stefan Ram Guest

    For simplification, I have not written:

    keyword0( object_pointer, kparm0 );
    keyword1( object_pointer, kparm0 );
    f( object_pointer, pos0, pos1 );
     
    Stefan Ram, Jun 2, 2014
    #28
  9. James Harris

    Phil Carmody Guest

    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
     
    Phil Carmody, Jun 3, 2014
    #29
  10. James Harris

    ais523 Guest

    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.
     
    ais523, Jun 3, 2014
    #30
  11. James Harris

    Noob Guest

    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.
     
    Noob, Jun 4, 2014
    #31
  12. 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>
     
    Ben Bacarisse, Jun 4, 2014
    #32
  13. James Harris

    James Kuyper Guest

    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.
    "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".
    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*).
    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.
     
    James Kuyper, Jun 4, 2014
    #33
  14. James Harris

    Ike Naar Guest

    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.
     
    Ike Naar, Jun 4, 2014
    #34
  15. James Harris

    Noob Guest

    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.
     
    Noob, Jun 4, 2014
    #35
  16. James Harris

    James Kuyper Guest

    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.
     
    James Kuyper, Jun 4, 2014
    #36
  17. James Harris

    Noob Guest

    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.
     
    Noob, Jun 4, 2014
    #37
  18. The second argument to strtod() is such.

    -- Richard
     
    Richard Tobin, Jun 4, 2014
    #38
  19. James Harris

    Noob Guest

    strtod is not very variadic.

    double strtod(const char *nptr, char **endptr);
     
    Noob, Jun 4, 2014
    #39
  20. It's obvious from the function's documentation. If it's not obvious,
    the function cannot be used safely.
    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.
    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.

    No, I'm suggesting they should read the function's documentation.
    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*.
     
    Keith Thompson, Jun 4, 2014
    #40
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.