[Из песочницы] Именованные аргументы функции в C

В некоторых языках существует возможность вызова функции с именованными параметрами. Такой способ позволяет указать аргумент для определённого параметра, связав его с именем параметра, а не с позицией. Это возможно, например, в 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

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; } Стало немного лучше, но все равно осталась проблема с параметрами по умолчанию, если они отличны от нуля. Так в примере выше Volume2 = 0, т.к. поле length по умолчанию проинициализировалось нулем. Еще за именованные аргументы мы платим тем, что должны создавать структуру или помнить ее название, если делаем приведение типов. Да и делать постоянно приведение типов неудобно. Но на помощь приходят…Вариативные макросы Макросы, которые принимают переменное число аргументов, появились в C99. Объявляются они также как и функция, которая принимает переменное число аргументов: нужно добавить многоточие в качестве последнего аргумента. Идентификатор __VA_ARGS__ заменяется аргументами, переданными в многоточии, включая запятые (точки с запятой) между ними. Сферический пример ниже. #include #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

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)); } Все примеры компилируется с флагом -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.Подробней про вариативный макрос написано, например, в ВикипедииИдея взята из книги Бена Клеменса

© Habrahabr.ru