В некоторых языках существует возможность вызова функции с именованными параметрами. Такой способ позволяет указать аргумент для определённого параметра, связав его с именем параметра, а не с позицией. Это возможно, например, в C# или Python.
Рассмотрим «игрушечный» пример на Python с использованием именованных аргументов:
#вычислим объем параллелепипеда
#если значение стороны не указано, то считаем что оно равно единице
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
Здесь в примере одна и та же функция вызывается с разными аргументами. И видно, какой параметр каким значением проинициализирован. Если у функции есть параметры, значения которых можно оставить по умолчанию, то очень удобно проинициализировать только необходимые параметры с помощью именованных аргументов. Но в языке C аргументы функции связаны с позицией, поэтому разработчику нужно помнить порядок следования параметров, что может быть неудобно, если их достаточно много.
Ниже я покажу, как можно сымитировать использование именованных аргументов в C.
Меньше слов — больше кода
Самое очевидное решение – передавать в функцию не разрозненный набор параметров, а структуру. Инициализировать ее удобно списком в фигурных скобках. Например:
#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 = %in", volume_f(p));
/* Volume2 = 0 */
printf("Volume2 = %in", volume_f( (params_s){.width=4, .height=2}) );
return 0;
}
Стало немного лучше, но все равно осталась проблема с параметрами по умолчанию, если они отличны от нуля. Так в примере выше Volume2 = 0, т.к. поле length по умолчанию проинициализировалось нулем. Еще за именованные аргументы мы платим тем, что должны создавать структуру или помнить ее название, если делаем приведение типов. Да и делать постоянно приведение типов неудобно. Но на помощь приходят…
Вариативные макросы
Макросы, которые принимают переменное число аргументов, появились в C99. Объявляются они также как и функция, которая принимает переменное число аргументов: нужно добавить многоточие в качестве последнего аргумента. Идентификатор __VA_ARGS__ заменяется аргументами, переданными в многоточии, включая запятые (точки с запятой) между ними. Сферический пример ниже.
#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;
}
После работы препроцессора макросы развернутся в такой код:
int main() {
{ double d[] = {1, 2, 3, 4, 5, 0} ; /*...*/};
{ puts("hello"); puts("world"); return 0;};
return 0;
}
Используя вариативный макрос, мы можем просто заранее проинициализировать структуру, а потом добавить то, что было передано в него.
Итог
Теперь, соединив все воедино, можно притвориться, что в C тоже есть возможность вызвать функцию, передав ей именованные аргументы.
В итоге получился такой код:
#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(): %in", volume());
printf("volume(.length = 2): %in", volume(.length =2 ));
printf("volume(.length = 2, .width = 3): %in",
volume(.length = 2, .width = 3));
printf("volume(.length = 2, .width = 3, .height =4): %in",
volume(.length =2, .width =3, .height =4));
}
Все примеры компилируется с флагом -std=c99 или -std=gnu99
Т.к. при вызове функции происходит переприсвоение значений полям структуры, то компиляторы выдают варнинг.
GCC выдаст:
warning: initialized field overwritten [-Woverride-init]
clang:
warning: initializer overrides prior initialization of this subobject [-Winitializer-overrides].
Если надо его отключить, используем соответственно флаги: -Wno-initializer-overrides для clang или -Wno-override-init для gcc.
Подробней про вариативный макрос написано, например, в Википедии
Идея взята из книги Бена Клеменса
Автор: dehasi