3

In C11, I could create a function which prototype would look like this:

void myVaFunc(const char* const conv, ...);

I could run it like this:

myVaFunc("ici", 1, "test", 2);

The function would know (after parsing the 1st parameter) that there are 3 additional parameters (4 with the initial one) with types consequently int, string(char pointer) and int. Easy, but not very elegant. Recently I have learned about the _Generic keyword, which allows to derive the type of a variable at the compilation time. I started to wonder either there is a way to combine the variadic functionality (not function anymore, since it always needs the 1st static parameter) and the _Generic functionality. Why? In order to remove the 1st parameter, which tells the function how to parse the others. Can a macro, which would be called like this exist?

MYVAFUNC(1, "test", 2);

And work in a same way as described earlier myVaFunc?

I am thinking about it for a while now, but cannot figure out either it is possible.

halfer
  • 19,824
  • 17
  • 99
  • 186
Łukasz Przeniosło
  • 2,725
  • 5
  • 38
  • 74
  • I don't completely understand what it is you're asking to be able to do, but it doesn't sound doable. Have you figured out how to use `_Generic` yet? – Christian Gibbons Aug 09 '18 at 19:29
  • I am trying to derive either it is possible to use variable amount of parameters function-like macros without providing the 1st, initial parameter. Yes I have already figured it out. – Łukasz Przeniosło Aug 09 '18 at 19:30
  • The best the macro could do is generate the `"ici"` string automatically, and then call the `myVaFunc` with that string and the other parameters. – user3386109 Aug 09 '18 at 19:37
  • The thing is that "ici" is just one example. It can as well be "iiccicic" or whatever else the user of the API would think of. – Łukasz Przeniosło Aug 09 '18 at 19:38
  • I do believe it would be more efficient in practice to have `MYVAFUNC(1, "test", 2)` expand to multiple calls specific to each each argument type, for example to `do { myfunc_int(1); myfunc_str("test"); myfunc_int(2); } while (0);`. This would reduce the complexity, and allow the compiler to generate better code. – Nominal Animal Aug 09 '18 at 22:24
  • How can the macro know for how many parameters to loop? – Łukasz Przeniosło Aug 10 '18 at 03:42
  • _Generic makes variadic functions 100% obsolete. Even before C11, variadic functions should be avoided like the plague, because they are so horribly unsafe. Just get rid of the variadic crap. The proper way to pass multiple parameters to a C function is `void func (some_struct* s);`. – Lundin Aug 10 '18 at 09:14
  • @Lundin but the problem is that the amount of parameters is not set and can vary. – Łukasz Przeniosło Aug 10 '18 at 09:22
  • @Bremen That's a fault of the program design then. If possible, re-design the program properly. – Lundin Aug 10 '18 at 09:24

1 Answers1

5

That is definitely possible but AFAIK, it requires some nontrivial macro magic (and it's difficult to make it work for an unlimited number of arguments).

In my project I have a BX_foreachc(What,...) macro that allows you to implement it with:

#include <stdio.h>
#define MYVAFUNC(...) /*replace puts with the actual consumer of the generated format string*/ \
    (MYVAFUNC__ptr=MYVAFUNC__buf, \
     BX_foreachc(MYVAFUNC__append,__VA_ARGS__), \
     *MYVAFUNC__ptr=0, \
      puts(MYVAFUNC__buf))
//impl.:
char MYVAFUNC__buf[128]; 
char *MYVAFUNC__ptr = MYVAFUNC__buf;
#define MYVAFUNC__append(X) *MYVAFUNC__ptr++ = _Generic(X,char*:'c',int:'i')

int main(void)
{
    MYVAFUNC(1,"foo",1,2,"bar",1,2,3) ; 
    //generates and consumes "iciiciii" and returns the return value of the consumer
}

The problematic part might be that my implementation of BX_foreachc (with a support for up to 127 arguments) is about 140 lines of cryptic, mostly generated code.

Here's a script that generates it and test-runs it on the above-posted main:

#!/bin/sh -eu
bx_def_BX_argc() #Define an arg-counting macro for Max $1 args (incl) #{{{
{
    local max_args=${1:-128} i
    printf '#define BX_argc(...) BX_argc_(X,##__VA_ARGS__) //{{{\n'
    printf '#define BX_argc_(...) BX_argc__(,##__VA_ARGS__,'
    i=$max_args; while [ $i -gt 0 ]; do printf $i,; i=$((i-1)); done
    printf '0,0)\n'
    printf '#define BX_argc__(_,'
    while [ $i -le $max_args ]; do printf _$i,; i=$((i+1)); done
    printf 'Cnt,...) Cnt //}}}\n'
} #}}}
bx_def_BX_foreach_() #{{{
{
    local Comma="$1" Max="${2:-128}"
    if [ -z "$Comma" ]; then
        echo "#define BX_foreachc_1(What, x, ...) What(x)"
    else
        echo "#define BX_foreach_1(Join, What, x, ...) What(x)"
    fi
    i=2; while [ $i -lt $Max ]; do
        if [ -z "$Comma" ]; then
            printf '#define BX_foreach_%d(Join,What,x,...) What(x) Join BX_paste(BX_foreach_%d(Join, What, __VA_ARGS__))\n' \
              $i $((i-1));
        else
            printf '#define BX_foreachc_%d(What,x,...) What(x) , BX_paste(BX_foreachc_%d(What, __VA_ARGS__))\n' \
              $i $((i-1));
        fi
    i=$((i+1)); done
} #}}}
{
cat <<EOF
#define BX_foreach(Join,What, ...) BX_foreach_(BX_argc(__VA_ARGS__), Join, What, __VA_ARGS__)
#define BX_foreachc(What, ...) BX_foreachc_(BX_argc(__VA_ARGS__), What, __VA_ARGS__)
#define BX_cat(X,...)  BX_cat_(X,__VA_ARGS__) //{{{
#define BX_cat_(X,...) X##__VA_ARGS__ //}}}
#define BX_paste(X) X

///
#define BX_foreach_(N, Join, What, ...) BX_paste(BX_cat(BX_foreach_, N)(Join, What, __VA_ARGS__))
#define BX_foreachc_(N,  What, ...) BX_paste(BX_cat(BX_foreachc_, N)( What, __VA_ARGS__))
EOF

#define BX_argc(...) BX_argc_(X,##__VA_ARGS__)
bx_def_BX_argc
bx_def_BX_foreach_ ''
bx_def_BX_foreach_ 1
} > foreach.h

cat > main.c <<'EOF'
#include "foreach.h" //generated header implementing BX_foreachc
#include <stdio.h>
#define MYVAFUNC(...) /*replace puts with the actual consumer of the generated format string*/ \
    (MYVAFUNC__ptr=MYVAFUNC__buf, \
     BX_foreachc(MYVAFUNC__append,__VA_ARGS__), \
     *MYVAFUNC__ptr=0, \
      puts(MYVAFUNC__buf))
//impl.:
char MYVAFUNC__buf[128]; 
char *MYVAFUNC__ptr = MYVAFUNC__buf;
#define MYVAFUNC__append(X) *MYVAFUNC__ptr++ = _Generic(X,char*:'c',int:'i')

int main(void)
{
    MYVAFUNC(1,"foo",1,2,"bar",1,2,3) ; 
    //generates and consumes "iciiciii" and returns the return value of the consumer
}
EOF

#compile and test-run
gcc main.c
./a.out

If you want to guard against overflowing the 127 maximum argument count, you could replace the above foreach-generated comma expression with an expression statement (nonstandard but common C extension) of the form:

({ 
    char buf[128];
    char *p=buf, *e = buf+sizeof(buf)-1;

    //foreach X:
        if(*p==e) return FAIL; else *p = _Generic(X,char*:'c', int:'i');
    *p = 0;
    puts(buf);
 })

An even better way to tackle this might be to completely forgo the format string an instead generate something like

do{
    //foreach X:
        if(FAILS(_Generic(X,char*: consume_str, int: consume_int)(X))) return FAIL;
}while(0);

Example, working code (no nonstandard C features):

#include <stdio.h>
#include "foreach.h"
#define FAILS(X) (0>(X))
#define FAIL (-1)
int consume_int(int X){ return printf("%d\n", X); }
int consume_str(char const* X){ return puts(X); }

#define MYVAFUNC(...) do{ BX_foreach(;,CONSUME_ARG,__VA_ARGS__); }while(0);
#define CONSUME_ARG(X) if(FAILS(_Generic(X, char*: consume_str, int:consume_int)(X)))
int main(void)
{
    MYVAFUNC(1,"foo",1,2,"bar",1,2,3) ; 
}

(Note that this uses BX_foreach (a macro that uses a custom joiner, in my case it's ;) rather than the BX_foreachc comma-based special case.)

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 2
    I was working on an answer that says pretty much the same thing. Now I don't need to finish it! – John Bollinger Aug 09 '18 at 20:07
  • Wow... I need a moment to understand this. I definetly dont need more parameters that lets say 16 to be ready for everything. It would be nice to show a the user that he extended this preset amount (probably in runtime, unless its possible at compile time). Thank you for answer, tommorow I will try to implement this and let you know. – Łukasz Przeniosło Aug 09 '18 at 20:11
  • @Bremen That'd be doable too but I believe it'd require at least expressions statements (which is a common C extension) and a different version of the foreach macro where you can parametrize the joiner (MC_foreachc is a special case that uses commas, which is what the c stands for). – Petr Skocik Aug 09 '18 at 20:21
  • @Bremen Let's forget about that. It'd make the answer way too long. – Petr Skocik Aug 09 '18 at 20:37
  • But since its something that would simplify the problem, why not do it? Also, I really intend to use this in a real program, its not just a theoretical question. If you only have the time, please introduce the idea. – Łukasz Przeniosło Aug 09 '18 at 20:39
  • 1
    Ok, this ia understandable. So tommorow I will try to implement the one you provided. Thank you. – Łukasz Przeniosło Aug 09 '18 at 20:47
  • The definition for `BX_foreachc` is lacking. – Łukasz Przeniosło Aug 10 '18 at 09:38
  • @Bremen BX_foreachc -> MC_foreachc. Now it compiles (with implicit puts(), better also include stdio.h). – Petr Skocik Aug 10 '18 at 09:48
  • @Bremen Reedited the answer, posted the code generator instead of the generated code and also added a note how to do it with buffer overflow checking and code on how to do it without format strings (also lost the MC prefixes and kept my project's original BX prefixes). – Petr Skocik Aug 10 '18 at 10:20