Ответ 1
Короткий вариант (вы уже заметили):
#define chkErr(FUNCTION, ...) \
if(!FUNCTION(__VA_ARGS__)) \
{ \
perror(#FUNCTION); \
}
Помните, что это может вызвать большие проблемы в вложенных if/else или подобных конструкциях:
if(x)
chkErr(f, 10, 12) //;
//^ semicolon forgotten!
else
chkErr(f, 12, 10);
будет компилировать код, эквивалентный следующему:
if(x)
{
if(!f(10, 12))
perror("f");
else if(!f, 12, 10))
perror("f");
}
Совершенно очевидно не то, что было предназначено для if/else, написанного с помощью макросов... Поэтому вы действительно должны предпочесть, чтобы он выглядел как настоящая функция (требующая точку с запятой):
#define chkErr(FUNCTION, ...) \
do \
{ \
if(!FUNCTION(__VA_ARGS__)) \
{ \
perror(#FUNCTION); \
} \
} \
while(0)
Вы бы назвали это следующим образом:
chkErr(someFunction, 10, 12);
В случае ошибки вывод будет:
someFunction: <error text>
Однако, это скрывает тот факт, что функция фактически вызвана, что затрудняет понимание для "аутсайдеров". Тот же вывод, не скрывающий вызов функции, но требующий дополнительной запятой между функцией и аргументами (по сравнению с обычным вызовом функции):
#define chkErr(FUNCTION, ARGUMENTS) \
do \
{ \
if(!FUNCTION ARGUMENTS) \
{ \
perror(#FUNCTION); \
} \
} \
while(0)
chkErr(someFunction,(12, 10));
// ^ (!)
Другой вариант с очарованием сохранения вызова функции распечатает весь этот вызов функции:
#define chkErr(FUNCTION_CALL) \
do \
{ \
if(!FUNCTION_CALL) \
{ \
perror(#FUNCTION_CALL); \
} \
} \
while(0)
chkErr(someFunction(10, 12));
В случае ошибки вывод будет:
someFunction(10, 12): <error text>
Добавление: Если вы действительно хотите точно выводить результат, как показано в вопросе, и все еще сохраняете вызов функции (без запятой между ними), вы немного в беде. На самом деле это возможно, но для этого требуется дополнительная работа:
Проблема в том, как препроцессор работает с аргументами макроса: каждый аргумент является токеном. Он может легко комбинировать токены, но не может их разделить.
Оставляя какие-либо запятые, получается, что макрос принимает один единственный токен, как и в моем втором варианте. Конечно, вы можете это сделать, как и я, но вы получите аргументы функции. Это строковый литерал, и поскольку предварительный процессор не может изменять строковые литералы, вы должны работать с ними во время выполнения.
Затем следующая проблема заключается в том, что строковые литералы не поддаются изменению. Поэтому вам нужно изменить копию!
Следующий вариант сделает все это для вас:
#define chkErr(FUNCTION_CALL) \
do \
{ \
if(!FUNCTION_CALL) \
{ \
char function_name[] = #FUNCTION_CALL; \
char* function_name_end = strchr(function_name, '('); \
if(function_name_end) \
*function_name_end = 0; \
perror(function_name); \
} \
} \
while(0)
Хорошо, решите, если это стоит усилий...
Кстати, пробелы между именем функции и открывающей скобкой не устраняются. Если вы хотите быть совершенным:
unsigned char* end = (unsigned char*) function_name;
while(*end && *end != '(' && !isspace(*end))
++end;
*end = 0;
Или, гораздо приятнее (спасибо chqrlie за подсказку):
function_name[strcspn(function_name, "( \t")] = 0;
Все, что я могу придумать, потребует дополнительного этапа предварительной обработки:
#define CAT(X, Y) CAT_(X, Y)
#define CAT_(X, Y) X ## Y
#define chkErr(FUNCTION_CALL) \
do \
{ \
if(!FUNCTION_CALL) \
{ \
perror(CAT(CHK_ERR_TEXT_, __LINE__)); \
} \
} \
while 0
chkErr(function(10, 12);
А, да, это приведет к следующему коду:
if(!function(10, 12))
{
perror(CHK_ERR_TEXT_42);
}
А теперь, откуда взять эти макросы? Ну, предварительная обработка, помните? Возможно, perl или python script, e. г. создавая дополнительный файл заголовка, который вы должны включить. Вам нужно будет убедиться, что эта предварительная обработка выполняется каждый раз до того, как выполняется предварительный процессор компилятора.
Ну, все не невозможно решить, но я оставлю это мазохистам среди нас...