На языке C есть ли способ использовать обратный вызов с произвольными/переменными аргументами?

Я хотел бы отправлять обратные вызовы с разными подписями для одной и той же функции. Что-то вроде этого:

#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;
    }
}

Но явно не работает :(

макрос будет соответствовать вашим потребностям? #define exec(f, ...) f(__VA_ARGS__)

tstanisl 21.10.2022 06:55
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
51
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете изменить обратные вызовы, чтобы они принимали один аргумент 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 к тому, где они хотели бы быть, учитывая предложенную конкретную сигнатуру обратного вызова, но это не то, что на самом деле спрашивали.

John Bollinger 21.10.2022 04:41
Ответ принят как подходящий

Обычное решение сделать интерфейсы функций обратного вызова гибкими в отношении данных, предоставляемых функции, состоит в том, чтобы задать сигнатуре обратного вызова параметр 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)

tstanisl 21.10.2022 09:15

Вы можете использовать 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));
}

Другие вопросы по теме