_Generic 结合可变参数函数? [英] _Generic combined with variadic function?

查看:30
本文介绍了_Generic 结合可变参数函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 C11 中,我可以创建一个原型如下所示的函数:

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

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

我可以这样运行:

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

函数会知道(在解析第一个参数后)还有 3 个附加参数(4 个与初始参数),其类型因此 int, string(char pointer) 和 int.简单,但不是很优雅.最近我了解了 _Generic 关键字,它允许在编译时派生变量的类型.我开始怀疑是否有一种方法可以将可变参数功能(不再是函数,因为它总是需要第一个静态参数)和 _Generic 功能结合起来.为什么?为了删除第一个参数,它告诉函数如何解析其他参数.可以这样调用的宏存在吗?

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);

和前面描述的一样工作 myVaFunc?

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.

推荐答案

这绝对是可能的,但 AFAIK,它需要一些不平凡的宏魔法(并且很难让它适用于无限数量的参数).

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).

在我的项目中,我有一个 BX_foreachc(What,...) 宏,允许您通过以下方式实现它:

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
}

有问题的部分可能是我的 BX_foreachc 实现(最多支持 127 个参数)大约有 140 行神秘的代码,大部分是生成的代码.

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.

这是一个生成它并在上面发布的 main 上测试运行它的脚本:

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

如果你想防止超过 127 个最大参数计数,您可以使用以下形式的表达式语句(非标准但常见的 C 扩展名)替换上述 foreach 生成的逗号表达式:

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);

示例,工作代码(无非标准 C 特性):

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

(请注意,这使用 BX_foreach(一个使用自定义连接器的宏,在我的例子中是 ;)而不是 BX_foreachc 基于逗号的特殊情况.)

(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.)

这篇关于_Generic 结合可变参数函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆