4

I'm refactoring a C++ application that refers to a bunch a multiple callback functions, each having a different number of arguments and I would like to know if there's a generic way to build a dedicated argument stack frame before calling each of them.

In other words: fetching all required arguments first according to function to be called, then perform a call that would be transparent for called function.

In a way, this is the opposite of a variadic function, as this would be a single function that knows it can receive different number of arguments. Here I have a bunch of immutable, regular functions and I'd like to call them from a generic hub.

I place both C and C++ tags here because I'm interested in propositions for both these languages, I have the feeling that what would work for C would also be applyable to C++ and I'm still open to "C++ only" solutions such as variadic templates or such.

The background of all of it is that quoted program is actually parsing a command line, then calling a function according to the name of the command, passed as first argument after program name, then all required ones as plain strings, but depending of invoked command.

I know I could write something like:

    if(nb_of_args == 1)
        my_callback_ptr(argv[n]);
    else if (nb_of_args == 2)
        my_callback_ptr(argv[n],argv[n+1]);
    else if (nb_of_args == 3)
        my_callback_ptr(argv[n],argv[n+1],argv[n+2]);
    else if (nb_of_args == 4)
        my_callback_ptr(argv[n],argv[n+1],argv[n+2],argv[n+3]);
    else if…

…to restrict the compiled calls to the sole number of arguments regardless of the function itself, but I'd still like to do better.

Thanks in advance to everybody.

Obsidian
  • 3,719
  • 8
  • 17
  • 30
  • 2
    Make your callbacks take arrays, not separate arguments. – Shawn Aug 07 '21 at 18:21
  • That's the point of the thing, unfortunately: I cannot change them. Too much of them, used in many different places apart of this one. Aside of this, the question itself remains interesting in itself. – Obsidian Aug 07 '21 at 18:23
  • I don't think this can be done in standard C. If the functions all take the same types, which fit with default argument promotions, and only the number of arguments differs, then on some systems you might get away with using a function pointer with no prototype: `void (*my_callback_ptr)(); my_callback_ptr(argv[n], argv[n+1], argv[n+2], argv[n+3]. etc);`. Generally it is UB to pass too many parameters in this way, but on many common systems the extras will be ignored. – Nate Eldredge Aug 07 '21 at 18:51
  • You might modify the callbacks as prototype `callback(size_t n, *argv[]);` then you should pass to the callbacks the number of parms and the pointer to the first parm ... as the C/C++ `main` uses ... (Ok I read you cannot! But I leave my comment) – Sir Jo Black Aug 07 '21 at 18:53
  • 1
    You could try variadic templates, later specialising them explicitly. It will make the code a bit messy, but you can just call `template void callback(Args...)` for all of them. If the arguments are deducible you don't have to worry about them either. It's not much better than the current workaround though. It might look like this: `template<> void callback(int a, int b){ my_callback_ptr(a,b); }` – Lala5th Aug 07 '21 at 18:59
  • 1
    Anything you can't handle with a plain `std:: bind`??? – Ext3h Aug 07 '21 at 19:48

2 Answers2

8

A viable approach is to implement dispatch table that will contain pointers to wrapper functions automatically passing appropriate amount of arguments to each callback.

#include <array>
#include <utility>
#include <cstddef>

template<auto x_p_callback, ::std::size_t... x_indexes>
void invoke_callback(char const * const * const pp_args, ::std::index_sequence<x_indexes...>)
{
    (*x_p_callback)(pp_args[x_indexes]...);
}

template<typename x_Callback >
struct
t_ArgsCount;

template<typename x_Result, typename... x_Args>
struct
t_ArgsCount<x_Result (x_Args...)>
:   ::std::integral_constant<::std::size_t, sizeof...(x_Args)>
{};

template<auto x_p_callback>
void callback_sunk(char const * const * const pp_args)
{
    using
    t_Callback = typename ::std::remove_reference<decltype(*x_p_callback)>::type;

    using
    t_Sequence = typename ::std::make_index_sequence<t_ArgsCount<t_Callback>::value>;

    invoke_callback<x_p_callback>(pp_args, t_Sequence{});
}

using
t_Callback = void (char const * const * const pp_args);

template<auto... x_p_callbacks>
constexpr auto make_table(void)
{
    return ::std::array<t_Callback *, sizeof...(x_p_callbacks)>{&callback_sunk<x_p_callbacks>...};
}

void my_callback_0() {}
void my_callback_1(char const *) {}
void my_callback_2(char const *, char const *) {}
void my_callback_3(char const *, char const *, char const *) {}

int main(int argc, char const * const * const pp_args)
{
    constexpr auto table{make_table<my_callback_0, my_callback_1, my_callback_2, my_callback_3>()};
    table.at(argc)(pp_args);
}

online compiler

user7860670
  • 35,849
  • 4
  • 58
  • 84
2

Although I have some doubts about the C code I wrote (all to be verified). To solve the difficulty to convert your callbacks with a prototype like callback(int argc, char *argv[] (as the C/C++ main), my idea has been to make the pointers into the functions table that indexes the callbacks as pointers to variadic functions.

In this way we may call all callback functions passing the maximum number of parameters by means the pointers in the table. Obviously the called functions will only use the parameters they need and not the others.

On the other hand, this code will consume instructions and, depending on the case, even stack memory to pass useless parameters to functions that do not need them.

This code seems to run correctly, but it has to be verified!

#include <stdio.h>

#define CBCALL(cbidx, argv, n) \
    ft[cbidx].f(argv[n],argv[n+1],argv[n+2],argv[n+3],argv[n+4])

int cb0();
int cb1(char *);
int cb2(char *, char *);
int cb3(char *, char *, char *);
int cb4(char *, char *, char *, char *);
int cb5(char *, char *, char *, char *, char *);

typedef int FN(char *,...);

struct table {
    FN * f;
} ft[]={
{(FN *)cb0},
{(FN *)cb1},
{(FN *)cb2},
{(FN *)cb3},
{(FN *)cb4},
{(FN *)cb5}
};


int main(int argc, char * argv[])
{
    if (argc<7) {
        CBCALL(argc-1,argv,1);

        if (argc>2)
        {// Trying with copied parms and NULL.
            char *xarg[5];
            xarg[0]=argv[1];
            xarg[1]=argv[2];
            xarg[2]=NULL;
            xarg[3]=NULL;
            xarg[4]=NULL;

            CBCALL(2,xarg,0);
        }

    } else {
        printf("Max 5 parameters are allowed!\n");
    }

    return 0;
}

int cb0()
{
    printf("No parms\n");
    return 0;
}

int cb1(char *a)
{
    printf("%s\n",a);
    return 1;
}

int cb2(char *a, char *b)
{
    printf("%s %s\n",a,b);
    return 2;
}

int cb3(char *a, char *b, char *c)
{
    printf("%s %s %s\n",a,b,c);
    return 3;
}

int cb4(char *a, char *b, char *c, char *d)
{
    printf("%s %s %s %s\n",a,b,c,d);
    return 4;
}

int cb5(char *a, char *b, char *c, char *d, char *e)
{
    printf("%s %s %s %s %s\n",a,b,c,d,e);
    return 5;
}
Sir Jo Black
  • 2,024
  • 2
  • 15
  • 22