📜 ⬆️ ⬇️

Named Function Arguments in C

In some languages, it is possible to call a function with named parameters. This method allows you to specify an argument for a particular parameter, associating it with the name of the parameter, and not with the position. This is possible, for example, in C # or Python.

Consider a “toy” Python example using named arguments:

#   #    ,       def volume(length=1, width=1, height=1): return length * width * height; print(volume()) # V = 1 print(volume(length=2)) # V = 2 print(volume(length=2, width=3)) # V = 6 print(volume(length=2, width=3, height=4)) # V = 24 

Here in the example, the same function is called with different arguments. And you can see which parameter is initialized by which value. If the function has parameters whose values ​​can be left as default, then it is very convenient to initialize only the necessary parameters using named arguments. But in the C language, the function arguments are associated with the position, so the developer needs to remember the order of the parameters, which can be inconvenient if there are enough of them.
')
Below, I will show you how to simulate the use of named arguments in C.

Less words - more code


The most obvious solution is to transfer to the function not a separate set of parameters, but a structure. It is convenient to initialize it with a list in curly brackets. For example:

 #include <stdio.h> typedef struct { int length, width, height; } params_s; int volume_f(params_s in) { return in. length * in. width * in.height ; } int main() { params_s p = {.length=8, .width=4, .height=2}; /* Volume1 = 64 */ printf("Volume1 = %i\n", volume_f(p)); /* Volume2 = 0 */ printf("Volume2 = %i\n", volume_f( (params_s){.width=4, .height=2}) ); return 0; } 

It got a little better, but still there was a problem with the default settings, if they are non-zero. So in the example above, Volume2 = 0, because The length field is by default initialized to zero. We also pay for the named arguments with the fact that we have to create a structure or remember its name if we do a type conversion. And it is inconvenient to do a permanent type cast But come to the rescue ...

Variable Macros


Macros that take a variable number of arguments appeared in C99. They are declared as well as a function that takes a variable number of arguments: you need to add an ellipsis as the last argument. The identifier __VA_ARGS__ is replaced by the arguments passed in ellipsis, including the commas (semicolons) between them. Spherical example below.

 #include <stdio.h> #define printArray(str, ...) { \ double d[] = {__VA_ARGS__, 0} ; \ puts(str); \ for(int i = 0; d[i] !=0; i++) \ printf("%g ", d[i]); \ puts(""); \ } #define DO(...){ __VA_ARGS__ } int main() { printArray("cool array: ", 1, 2, 3, 4, 5); /*  ,        */ DO(puts("hello"); puts("world"); return 0); return 0; } 

After the preprocessor's work, the macros will unfold into the following code:

 int main() { { double d[] = {1, 2, 3, 4, 5, 0} ; /*...*/}; { puts("hello"); puts("world"); return 0;}; return 0; } 

Using a variable macro, we can simply pre-initialize the structure, and then add what has been transferred to it.

Total


Now, by putting everything together, you can pretend that C also has the ability to call a function, passing named arguments to it.

The result is the following code:

 #include <stdio.h> typedef struct { int length, width, height; } params_s; int volume_f(params_s in) { return in. length * in.width * in.height ; } #define volume(...) \ volume_f((params_s){.length=1, .width=1, .height=1 , __VA_ARGS__}) int main() { printf("volume(): %i\n", volume()); printf("volume(.length = 2): %i\n", volume(.length =2 )); printf("volume(.length = 2, .width = 3): %i\n", volume(.length = 2, .width = 3)); printf("volume(.length = 2, .width = 3, .height =4): %i\n", volume(.length =2, .width =3, .height =4)); } 

All examples are compiled with the -std = c99 or -std = gnu99 flag
Since when the function is called, the values ​​are reassigned to the fields of the structure, then the compilers issue a varning.

GCC will issue:

 warning: initialized field overwritten [-Woverride-init] clang: warning: initializer overrides prior initialization of this subobject [-Winitializer-overrides]. 

If you need to disable it, use the flags accordingly: -Wno-initializer-overrides for clang or -Wno-override-init for gcc.

More details about the variable macro are written, for example, on Wikipedia.
The idea is taken from the book of Ben Clemens

Source: https://habr.com/ru/post/248385/


All Articles