Я хотел бы отправлять обратные вызовы с разными подписями для одной и той же функции. Что-то вроде этого:
#include <stdio.h>
#include <stdarg.h>
void a(int pa) {}
void b(int pb1, float pb2) {}
// exec implementation
int main() {
exec(a, 1);
exec(b, 1, 2.3);
}
Я думал об использовании чего-то вроде:
void exec(void (*func)(...), ...) {
int arg1;
float arg2;
va_list valist;
va_start(valist, size);
arg1 = va_arg(valist, int);
if (size == 1) {
(*func)(arg1);
va_end(valist);
return;
}
arg2 = va_arg(valist, float);
if (size == 2) {
(*func)(arg1, arg2);
va_end(valist);
return;
}
}
Но явно не работает :(





Вы можете изменить обратные вызовы, чтобы они принимали один аргумент va_list:
void a(va_list args)
{
int pa = va_arg(args,int);
}
void b(va_list args)
{
int pb1 = va_arg(args,int);
double pb2 = va_arg(args,double);
}
И пусть ваша другая функция передаст va_list вместе.
void exec(void (*func)(va_list), ...)
{
va_list valist;
va_start(valist, func);
func(valist);
va_end(valist);
}
Но это противоречит заявленной цели. Вместо обратных вызовов с различными сигнатурами он поддерживает только одну сигнатуру обратного вызова. Это все еще может приблизить OP к тому, где они хотели бы быть, учитывая предложенную конкретную сигнатуру обратного вызова, но это не то, что на самом деле спрашивали.
Обычное решение сделать интерфейсы функций обратного вызова гибкими в отношении данных, предоставляемых функции, состоит в том, чтобы задать сигнатуре обратного вызова параметр void * (возможно, в дополнение к другим параметрам). Через такой параметр могут быть предоставлены произвольные данные. Что-то вроде этого:
void exec(void (*func)(void *), void *data) {
func(data);
}
struct s2 {
int i;
float f;
};
void func1(void *data) {
int i = *(int *)data;
// ...
}
void func2(void *data) {
struct s2 s = *(struct s2 *)data;
// ...
}
int main(void) {
int i = 42;
struct s2 s = { .i = 17, .f = 3.14 };
exec(func1, &i);
exec(func2, &s);
}
ОДНАКО, можно сделать что-то более похожее на то, что вы описываете, где функции обратного вызова действительно имеют разные подписи, указав тип обратного вызова без прототипа. В этом случае есть еще как минимум следующие предостережения:
Если сами функции обратного вызова определены с помощью прототипов (как они и должны быть), то типы параметров не должны быть такими, которые изменены продвижением аргументов по умолчанию. Итак, указатели, ints, doubles, но не floats или short ints или chars (не исчерпывающий список). Если вы хотите поддерживать другие типы параметров, вам нужно будет преобразовать указатель функции перед вызовом функции, как описано ниже.
Функции обратного вызова не могут быть переменными.
Если внешний интерфейс является вариативным, то ему нужно каким-то образом сообщить во время выполнения, каковы фактическое количество и типы аргументов.
Кроме того, потребуются явные вызовы функций обратного вызова с правильными аргументами, поэтому может поддерживаться только фиксированный набор предопределенных сигнатур обратного вызова.
Например, это может выглядеть примерно так:
enum sig { INT, INT_DOUB };
void exec(void (*func)(/* no prototype */), enum sig cb_sig, ...);
void a(int pa) {}
void b(int pb1, double pb2) {}
int main(void) {
exec(a, INT, 1);
exec(b, INT_DOUB, 1, 2.3);
}
void exec(void (*func)(/* no prototype */), enum sig cb_sig, ...) {
va_list valist;
va_start(valist, cb_sig);
switch (cb_sig) {
case INT: {
int i = va_arg(valist, int);
func(i);
break;
}
case INT_DOUB: {
int i = va_arg(valist, int);
double d = va_arg(valist, double);
func(i, d);
break;
}
default:
assert(("Can't be reached", 0));
}
va_end(valist);
}
Возможно, это вызовет несколько предупреждений, например, об объявлении функции, которое не предоставляет прототип, и о вызове (объявленной, но) непрототипированной функции. Однако, поскольку вы знаете сигнатуры к моменту выполнения вызовов, вы можете избавиться от предупреждений последнего типа с помощью соответствующего приведения типов. Например,
// ...
case INT: {
int i = va_arg(valist, int);
((void (*)(int))func)(i);
break;
}
// ...
Афаик, функции без прототипов будут удалены в C2x. Они станут (void)
Вы можете использовать va_args, чтобы решить эту проблему.
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#define exec_func(func, ...) func(__VA_ARGS__)
long func(char *a, int b, long c, long d)
{
printf("a: %s, b: %d, c: %ld, d: %ld\n", a, b, c, d);
return c + d;
}
int main()
{
printf("c + d: %ld\n", exec_func(func, "test", 10, 1000, 1000));
}
макрос будет соответствовать вашим потребностям?
#define exec(f, ...) f(__VA_ARGS__)