2

I am aware it is generally not possible to forward variadic arguments from one variadic function to another: Forward an invocation of a variadic function in C http://c-faq.com/varargs/handoff.html

My question is, is it possible if I know the number of variadic args in the callee? For example, can f_wrapper be written to pass ... arguments to f?:

int f_wrapper(int argc, ...){ 
  /* argc is the number of variadic args f_wrapper is called with. Can it be used to call f? */
  /* Example: f_wrapper(3, "arg1", "arg2", "arg3"); */
}
int f(...){
  /* f must be called with all ... args from f_wrapper. */
  /* Adding signature with va_list is NOT allowed. c++ is NOT allowed */
}
  • 3
    You can implement the wrapper as a variadic macro instead. – Lundin Jun 03 '22 at 13:55
  • You can actually do that by a hacky way using `memcpy` but code may become unportable – Madhusoodan P Jun 03 '22 at 13:56
  • 2
    It's so common that parameters are passed in registers that I'm not sure the `memcpy` can be implemented even if you know what's going on under the hood. – Steve Friedl Jun 03 '22 at 13:57
  • 1
    Tanner Firl, Curious: `f_wrapper()` knowns how many arguments it received due to `argc`. How does `f()` knowns how many arguments it received? – chux - Reinstate Monica Jun 03 '22 at 14:39
  • 1
    Tanner Firl, knowing your higher level application goal would help as one could then offer a better solution that what your are trying to do. – chux - Reinstate Monica Jun 03 '22 at 14:41
  • `f(...)` constructs a va_list-like list from the `...` it receives and forwards it on. It doesn't need to know how many there are. My application has no control over `f(...)` but I must wrap it with the exact signature. – Tanner Firl Jun 03 '22 at 20:23
  • Not sure what you had in mind with the variadic macro, but the wrapper needs to be callable from other shared objects, so I don't think a variadic macro would work regardless. Is that correct? – Tanner Firl Jun 03 '22 at 20:51

1 Answers1

3

My question is, is it possible if I know the number of variadic args in the callee? For example, can f_wrapper be written to pass ... arguments to f?

The basic problem is that forwarding variadic arguments from one variadic function to another is a lexical issue, not a data processing issue. You must write a call to the wrapped function in which there is a compile-time representation of each argument, but you don't know until run time how many there will be, and you might not know their types.

If the space of acceptable argument lists is constrained enough, then the problem is pragmatically solvable. For example, if the wrapper will permit only a small number of variadic arguments, of only a small number of types (that it knows how to recognize), then it can analyze the variadic arguments at runtime to match them to the appropriate one of a list of possible calls to the wrapped function. Something like this:

int f_wrapper(int argc, ...) {
    if (argc < 0 || argc > F_ARGS_MAX) {
        // unsupported number of arguments
        return -1;
    }

    int arg_values[F_ARGS_MAX];
    // ... read argc variadic arguments into arg_values ...

    switch (argc) {
        case 0:
            return f();
        case 1:
            return f(arg_values[0]);
        // ...
    }
}

Obviously, however, that gets impractical rather quickly, and it can't cope at all if there is no upper bound on the acceptable number of arguments.

As was suggested in comments, a variadic macro may be a more viable alternative, but that's not a complete substitute. For example, you cannot construct a pointer to a macro, you cannot use such a macro to provide an external interface to a function with internal linkage, the macro cannot host a static variable in the same way that a function does, ....

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • don't you need to `return f(arg_values[0], arg_values[1], ..., arg_values[F_ARGS_MAX - 1])` in all cases instead of `return f(arg_values[0])`? Those aren't equivalent are they? The former approach was the closest I could get to a solution. I can set `F_ARGS_MAX` high enough to accomodate all reasonable situations, but if I set it too high, performance becomes an issue. Thoughts on which approximate solution would be faster? – Tanner Firl Jun 03 '22 at 20:36
  • Oh, didn't notice the `// ...`. So, you'd have `case 0:` through `case F_ARGS_MAX:`. Which would be faster than always passing `F_ARGS_MAX` args since switches utilizes jumps under the hood, correct? Now that I think about it, my solution relies on `f` not care about any args past `argc`, so its less ideal in that situation as well. – Tanner Firl Jun 03 '22 at 21:00
  • Could you also explain why its ok to pass a list of `int` args? I would think that the types passed to `f_wrapper`'s `...` would need to be retained when passed onto `f`'s `...` – Tanner Firl Jun 04 '22 at 01:14
  • @TannerFirl, you seem to be reading too much into the details of example code presented. In particular, yes, you *do* need to forward the arguments type-correctly. The example code directly addresses only the family of variadic functions in which all the variadic arguments are (semantically) required to be `int`s. The prose does talk about the need to type-match the forwarded arguments to an appropriate call. Overall, you should be reading this as "you can do it only in a few very special cases". – John Bollinger Jun 04 '22 at 02:20
  • Thank you. I realized that, without the assumption of all `int` types being passed to `f_wrapper`, this code depends on the compiler compiling the code in a particuarly "typical" way. But as you say there is no guarantee it will work properly. – Tanner Firl Jun 05 '22 at 17:12