Hacker News new | past | comments | ask | show | jobs | submit login
Named parameters in C (noctua-software.com)
65 points by guillaumec on July 14, 2014 | hide | past | favorite | 35 comments

Named arguments can be done by passing a struct as the only parameter to a function, without the complicated macros. Missing arguments become 0, so you get optional parameters for free.

    func((struct somestruct){.x = 0, .y = 1});

That is really cool.

Combine this with struct definitions in function bodies and I see a great teamup with variadic functions.

Take a theoretical struct_printf, which prints out non promoted data with struct packing (not variadic stack packing, which requires type promotion)

  /* Only uses va_start and va_arg to*/
  void struct_printf(const char* format, ...){

  void print_foo(){
    struct foo_t{
      float f;
      uint16_t u2;
      uint8_t x;
    } input;

    struct_printf("No type promotion here: %f %hu %hhx\n",
     (struct foo_t){ .f = 0.1, .u2 = 1000, .x = 0xF2});
Now we can actually pass what we mean to pass without the compiler changing it.

NOTE: After I wrote this I'm not actually sure what va_args does with a struct or how to get a pointer to the first member...

NOTE: After I wrote this I'm not actually sure what va_args does with a struct or how to get a pointer to the first member...

I think you would still need to know the exact type inside of struct_printf(), unless you knew exactly how the compiler would pack a struct onto the stack or registers, and that it was exactly the same as individual parameters.

Note, %hu and %hhx are not portable ways to print uint16_t and uint8_t.

Instead, you're supposed to #include <inttypes.h> and use:

  "No type promotion here: %f %"PRIu16" %"PRIx8"\n"

And that's almost exactly what the macro expands to. It's just some nice syntactic sugar on top.

The function declaration with the macro is a bit awkward, IMO. I don't mean to downplay the awesomeness of the macro approach, though; FOR_EACH and putting __VA_ARGS__ at the beginning of the macro argument list isn't something I had considered before.

This (compound literals) also has the advantage of being strictly valid C, since compound statements are a GNU extension.

I find your statement puzzling. A GNU extension is by definition not “strictly valid C”, compound literals are valid C because they are defined in C99.

Compound literals are valid C; compound statements, used in the macros in the article, are a GNU extension.

Oh right, I misread the previous comment, thanks.

What's the point in that? Then you have to write all your functions to accept structs instead of natural arguments.


Sometimes it's a good way to go.

...agreed, what's the point? Besides, a lot of includes are already a mess of #defines, do we really need another bunch of them?

Wow, that's really interesting. I had no idea this was possible. Do you know of any links to tutorials or blog posts on this? I'd love to see a more detailed example.

A value of 0 is not the same thing as a missing value.

That's true, but the parent comment never claimed that, only that missing arguments are assigned the value 0 in the resultant struct.

“[…] so you get optional parameters for free”

I should have finished that with "... as long as a default of 0 is acceptable." If 0 is a valid argument but a bad default, then it's slightly less useful.

Clever, but seems like a solution in a search of a problem.

The problem is that arguments are often ambiguous without the accompanying function declaration. (And the problem's exasperated when there are lots of arguments.)

The solution is named arguments, but I'm not convinced the added complexity of this solution is a net improvement.

Maybe there are some situations in which named arguments could be a genuine improvement, but I think in general it just encourages sloppiness and unneeded verbosity. It encourages writing functions that needlessly take way too many parameters (can't you instead think of a way to simplify the problem?). It encourages calling functions without paying attention to all the parameters, which you usually shouldn't ever ignore. Just like you shouldn't arbitrarily ignore return values. If you don't know your function like the back of your hand, you need documentation. Of course, documentation might be sparse in sloppy code that encourages people to ignore the details...

C is like that really. You really need to pay attention to detail when you're writing C. And if there's too much to worry about, it means you need to redesign and simplify the APIs and/or data you're dealing with.

If I encounter functions with lots of parameters in third party libraries I have to use, I will annotate each argument with a comment (after checking the documentation).

I find this problem exacerbated by using a lot of primitive types, and ameliorated (though not eliminated) by wrapping things in context-specific single element structs.


    int price, quantity;

    place_order(price, quantity);

        price_t px = { price };
        quantity_t qx = { quantity };

        place_order(qx, px);

More verbose (especially where, like above, the values weren't already wrapped), but catches a significant error.

It does not permit reordering arguments like the struct approach does, and doesn't help where you have multiple arguments of the same type that it is unreasonable to distinguish.

When writing in C, usually you either know the function well enough (including having just written it) to have memorised the arguments, have another window containing the declarations, or else you'll be using an IDE that tells you what the arguments are (along with a lot of other information.)

The most frustrating thing in C or C++ is when a header file is missing all of the argument names, so the declarations are just

    OVERKILL_TYPEDEF_PTR_T somefunc(TYPE1_T, TYPE2_PTR_T, void *);
I always document my functions in the headers, especially for private code only I will use. E.g.

     * Like vfprintf(), but prepends a timestamp and the name of the current thread
     * or process.  The output FILE will be locked using lockf() while the
     * timestamp, thread name, and message are written.  The thread name can be set
     * using set_threadname().
    int vfptmf(FILE *out, const char *format, va_list args);

If you are writing in C, cryptic and unnecessary truncation is half the fun!

I think it is an interesting article as a lesson on how macro expansion works.

It is however, a terrible example of how to write good code. Any code that hides the language or manipulates for syntactic sugar only adds complexity, bugs and decreases the maintenability of the code.

As someone already said, a solution in search of a problem.

It's not (strictly) C, though.

Doesn't work in MSVC though.

That's not a proper C compiler though, as Microsoft like to point out regularly as a lame excuse...

Yet enough people think it's a C compiler that if you try to use avant garde C99 features in your C project, someone will invariably complain that MSVC doesn't build it.

.. thus giving up C and moving on to C# instead. (Students, I'm looking at you..)

MSVC is actually called MSVC++.

Microsoft is committed to C++, as a means to improve systems programming safety with more modern languages.

Besides, the code makes use of GCC extensions.

The article specifically states that it requires C99, so of course it doesn't.

Yes, designated initializers were added in ISO C99 and since Visual Studio 2013 they are also supported by MSVC.

But the code is neither ISO C99 nor C11. It uses a GNU C extension called “statement expressions”, which is not supported by MSVC.

Also, its preprocessor has quite a few problems. I think after removing statement exprs and running the code through a different PP, MSVC will compile it.

Of course, one could just simply use MinGW or Clang instead.

C99 compiler won't do, it also requires a GCC extension (statement expressions).

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact