C11中多参数C函数的generics
我理解单参数函数的C11generics,如下所示:(从这里 )
#define acos(X) _Generic((X), \ long double complex: cacosl, \ double complex: cacos, \ float complex: cacosf, \ long double: acosl, \ float: acosf, \ default: acos \ )(X)
但是,对于具有两个参数的函数来说似乎很痛苦,你需要将调用嵌套到_Generic
,这真的很难看; 摘自同一篇博客:
#define pow(x, y) _Generic((x), \ long double complex: cpowl, \ double complex: _Generic((y), \ long double complex: cpowl, \ default: cpow), \ float complex: _Generic((y), \ long double complex: cpowl, \ double complex: cpow, \ default: cpowf), \ long double: _Generic((y), \ long double complex: cpowl, \ double complex: cpow, \ float complex: cpowf, \ default: powl), \ default: _Generic((y), \ long double complex: cpowl, \ double complex: cpow, \ float complex: cpowf, \ long double: powl, \ default: pow), \ float: _Generic((y), \ long double complex: cpowl, \ double complex: cpow, \ float complex: cpowf, \ long double: powl, \ float: powf, \ default: pow) \ )(x, y)
有没有办法为多参数函数提供更多人类可读的generics,例如:
#define plop(a,b) _Generic((a,b), \ (int,long): plopii, \ (double,short int): plopdd)(a,b)
提前感谢您的回复。 基本的想法是为_Generic
一个宏包装器。
鉴于没有评估_Generic
的控制表达式,我建议应用一些算术运算来进行适当的类型组合,并打开结果。 从而:
#define OP(x, y) _Generic((x) + (y), \ long double complex: LDC_OP(x, y), \ double complex: DC_OP(x, y), \ ... )
当然,这仅适用于某些情况,但您可以随时展开“折叠”类型无效的那些情况。 (例如,这将使用链接的printnl
示例来处理数组-N-of-char vs char *
,然后如果组合类型为int
,则可以返回并检查char
和short
。)
由于C没有元组,让我们创建自己的元组:
typedef struct {int _;} T_double_double; typedef struct {int _;} T_double_int; typedef struct {int _;} T_int_double; typedef struct {int _;} T_int_int; typedef struct { T_double_double Double; T_double_int Int;} T_double; typedef struct { T_int_double Double; T_int_int Int;} T_int; #define typeof1(X) \ _Generic( (X), \ int: (T_int){{0}}, \ double: (T_double){{0}} ) #define typeof2(X, Y) \ _Generic( (Y), \ int: typeof1(X).Int, \ double: typeof1(X).Double )
这是客户端代码:
#include #include "generics.h" #define typename(X, Y) \ _Generic( typeof2(X, Y), \ T_int_int: "int, int\n", \ T_int_double: "int, double\n", \ T_double_double: "double, double\n", \ T_double_int: "double, int\n", \ default: "Something else\n" ) int main() { printf(typename(1, 2)); printf(typename(1, 2.0)); printf(typename(1.0, 2.0)); printf(typename(1.0, 2)); return 0; }
它有效:
~/workspace$ clang -Wall -std=c11 temp.c ~/workspace$ ./a.out int, int int, double double, double double, int
是的,您仍然需要以指数大小编写代码。 但至少你可以重复使用它。
这是一个版本,只需要您手动编写线性数量的代码,所有这些代码都与手头的事物直接相关(没有手工定义类型的大树)。 一,用法示例:
#include // implementations of print void print_ii(int a, int b) { printf("int, int\n"); } void print_id(int a, double b) { printf("int, double\n"); } void print_di(double a, int b) { printf("double, int\n"); } void print_dd(double a, double b) { printf("double, double\n"); } void print_iii(int a, int b, int c) { printf("int, int, int\n"); } void print_default(void) { printf("unknown arguments\n"); } // declare as overloaded #define print(...) OVERLOAD(print, (__VA_ARGS__), \ (print_ii, (int, int)), \ (print_id, (int, double)), \ (print_di, (double, int)), \ (print_dd, (double, double)), \ (print_iii, (int, int, int)) \ ) #define OVERLOAD_ARG_TYPES (int, double) #define OVERLOAD_FUNCTIONS (print) #include "activate-overloads.h" int main(void) { print(44, 47); // prints "int, int" print(4.4, 47); // prints "double, int" print(1, 2, 3); // prints "int, int, int" print(""); // prints "unknown arguments" }
这可能是您将要获得的最轻的语法。
现在针对缺点/限制:
- 你需要为列表中的重载函数声明所有参数类型
OVERLOADED_ARG_TYPES
- 参数类型必须是一个单词名称(不是一个大问题,感谢typedef,但需要记住)
- 这会导致实际调用站点出现巨大的代码膨胀(尽管编译器很容易实现优化 – GCC将在
-O1
) - 依赖于庞大的PP库(见下文)
您还必须定义一个不带参数的X_default
函数; 不要将它添加到重载声明块。 这用于非匹配(如果你想直接调用它,用任何不匹配的值调用重载,比如复合文字匿名结构或其他东西)。
这是activate-overloads.h
:
// activate-overloads.h #include #define ORDER_PP_DEF_8dispatch_overload ORDER_PP_FN( \ 8fn(8N, 8V, \ 8do( \ 8print( 8cat(8(static inline int DISPATCH_OVER_), 8N) ((int ac, int av[]) { return ) ), \ 8seq_for_each_with_idx( \ 8fn(8I, 8T, \ 8let( (8S, 8tuple_to_seq(8tuple_at_1(8T))), \ 8print( 8lparen (ac==) 8to_lit(8seq_size(8S)) ), \ 8seq_for_each_with_idx(8fn(8I, 8T, 8print( (&&av[) 8I (]==) 8cat(8(K_), 8T) )), 0, 8S), \ 8print( 8rparen (?) 8I (:) ) \ )), \ 1, 8V), \ 8print( ( -1; }) ) \ ) )) #define TYPES_TO_ENUMS(TS) ORDER_PP ( \ 8do( \ 8seq_for_each(8fn(8T, 8print( 8T (:) 8cat(8(K_), 8T) (,) )), \ 8tuple_to_seq(8(TS))), \ 8print( (default: -1) ) \ ) \ ) #define ENUMERATE_TYPES(TS) enum OVERLOAD_TYPEK { ORDER_PP ( \ 8seq_for_each(8fn(8V, 8print( 8V (,) )), 8types_to_vals(8tuple_to_seq(8(TS)))) \ ) }; #define ORDER_PP_DEF_8types_to_vals ORDER_PP_FN( \ 8fn(8S, 8seq_map(8fn(8T, 8cat(8(K_), 8T)), 8S)) ) ENUMERATE_TYPES(OVERLOAD_ARG_TYPES) #define OVER_ARG_TYPE(V) _Generic((V), TYPES_TO_ENUMS(OVERLOAD_ARG_TYPES) ) #define OVERLOAD ORDER_PP ( 8seq_for_each( 8fn(8F, 8lets( (8D, 8expand(8adjoin( 8F, 8(()) ))) (8O, 8seq_drop(2, 8tuple_to_seq(8D))), 8dispatch_overload(8F, 8O) )), 8tuple_to_seq(8(OVERLOAD_FUNCTIONS)) ) ) #undef OVERLOAD #define OVERLOAD(N, ARGS, ...) ORDER_PP ( \ 8do( \ 8print(8lparen), \ 8seq_for_each_with_idx( \ 8fn(8I, 8T, \ 8lets( (8S, 8tuple_to_seq(8tuple_at_1(8T))) \ (8R, 8tuple_to_seq(8(ARGS))) \ (8N, 8tuple_at_0(8T)), \ 8if(8equal(8seq_size(8S), 8seq_size(8R)), \ 8do( \ 8print( 8lparen (DISPATCH_OVER_##N) 8lparen 8to_lit(8seq_size(8R)) (,(int[]){) ), \ 8seq_for_each(8fn(8A, 8print( (OVER_ARG_TYPE) 8lparen 8A 8rparen (,) )), 8R), \ 8print( (-1}) 8rparen (==) 8I 8rparen (?) 8N 8lparen ), \ 8let( (8P, 8fn(8A, 8T, \ 8print( (_Generic) 8lparen 8lparen 8A 8rparen (,) 8T (:) 8A (,default:*) 8lparen 8T (*) 8rparen (0) 8rparen ) \ )), \ 8ap(8P, 8seq_head(8R), 8seq_head(8S)), \ 8seq_pair_with(8fn(8A, 8T, 8do(8print((,)), 8ap(8P, 8A, 8T))), 8seq_tail(8R), 8seq_tail(8S)) \ ), \ 8print( 8rparen (:) ) \ ), \ 8print(( )) ) \ )), \ 1, 8tuple_to_seq(8((__VA_ARGS__))) \ ), \ 8print( 8cat(8(N), 8(_default)) (()) 8rparen) \ ) \ )
这需要Vesa K提供的令人敬畏的Order预处理器库 。
它是如何工作的: OVERLOAD_ARG_TYPES
声明用于构建枚举,列出用作常量的所有参数类型。 然后,可以通过在所有实现(右参数号)之间调度的大三元运算在调用者代码中替换对重载名称的每次调用。 调度通过使用_Generic
从参数类型生成枚举值,将它们放在数组中,并使用自动生成的调度程序函数返回该类型组合的ID(原始块中的位置)来工作。 如果ID匹配,则调用该函数。 如果参数的类型错误,则会为未使用的实现调用生成虚拟值,以避免类型不匹配。
从技术上讲,这涉及“运行时”调度,但由于每个类型ID都是常量而调度程序函数是static inline
,因此编译器应该很容易优化,除了有用的调用(并且GCC确实优化了所有调用)远)。
这是先前在此发布的技术的改进(相同的想法,现在具有漂亮和超轻的语法)。
哦……这是使用boost预处理器库(符合C99预处理器)的宏解决方案的开始。
我们的想法是提供一种通用语法,允许为任意数量的参数编写嵌套通用选择。 为了保持“简单”,选择的表达式对于同一选择级别上的所有元素是相同的(您可以定义另一种语法来单独更改每个级别选择上的控制表达式。)。
来自OP的这个例子
#define plop(a,b) _Generic((a,b), \ (int,long): plopii, \ (double,short int): plopdd)(a,b)
变
#define plop(a,b) \ MULT_GENERIC((a,b), \ (int, (long, plopii)), \ (double, (short int, plopdd)) \ )(a,b)
虽然我想可以稍微改变一下,以获得类似的东西:
#define plop(a,b) \ MULT_GENERIC((a,b), \ (int, long: plopii), \ (double, short int: plopdd) \ )(a,b)
哪个可以扩展为三个参数:
#define plop(a,b,c) \ MULT_GENERIC((a,b,c), \ (int, (double, long: plopidl, int: plopidi)), \ (double, (short int, long: plopdsl)) \ )(a,b)
进一步评论:我认为OP的语法也可以完成,但它不够灵活,因为你必须为每个可能的第二个参数重复第一个参数,例如
#define plop(a,b) _Generic((a,b), \ (int,long): plopii, \ (int,double): plobid \ (double,short int): plopdd)(a,b)
OP在我的语法中的例子。 请注意,您在这里获得的收益并不多,因为您仍需要特别指定每种类型,在这种情况下,第二种类型会针对不同的第一类型多次指定。
#define pow(x, y) MULT_GENERIC( \ (x, y), \ (long double complex, (default, cpowl) \ ), \ (double complex, (long double complex, cpowl) \ , (default, cpow) \ ), \ (float complex, (long double complex, cpowl) \ , (double complex, cpow) \ , (default, cpowf) \ ), \ (long double, (long double complex, cpowl) \ , (double complex, cpow) \ , (float complex, cpowf) \ , (default, powl) \ ), \ (default, (long double complex, cpowl) \ , (double complex, cpow) \ , (float complex, cpowf) \ , (long double, powl) \ , (default, pow) \ ), \ (float, (long double complex, cpowl) \ , (double complex, cpow) \ , (float complex, cpowf) \ , (long double, powl) \ , (float, powf) \ , (default, pow) \ ) \ ) \ (x, y) pow(x, y)
这解决了:
_Generic( (x), long double complex : _Generic( (y), default : cpowl ) , double complex : _Generic( (y), long double complex : cpowl , default : cpow ) , float complex : _Generic( (y), long double complex : cpowl , double complex : cpow , default : cpowf ) , long double : _Generic( (y), long double complex : cpowl , double complex : cpow , float complex : cpowf , default : powl ) , default : _Generic( (y), long double complex : cpowl , double complex : cpow , float complex : cpowf , long double : powl , default : pow ) , float : _Generic( (y), long double complex : cpowl , double complex : cpow , float complex : cpowf , long double : powl , float : powf , default : pow ) ) (x, y)
哪个是重新格式化的:
_Generic((x), long double complex: _Generic((y), default: cpowl) , double complex: _Generic((y), long double complex: cpowl , default: cpow) , float complex: _Generic((y), long double complex: cpowl , double complex: cpow , default: cpowf) , long double: _Generic((y), long double complex: cpowl , double complex: cpow , float complex: cpowf , default: powl) , default: _Generic((y), long double complex: cpowl , double complex: cpow , float complex: cpowf , long double: powl , default: pow) , float: _Generic((y) , long double complex: cpowl , double complex: cpow , float complex: cpowf , long double: powl , float : powf , default: pow) ) (x, y)
由于递归性质,我不得不介绍宏的副本; 这个解决方案还需要清理(我有点累)。 宏:
#include #define MULT_GENERIC_GET_ASSOC_SEQ(DATA_TUPLE) \ BOOST_PP_TUPLE_ELEM(2, DATA_TUPLE) #define MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE) \ BOOST_PP_SEQ_ELEM( N, MULT_GENERIC_GET_ASSOC_SEQ(DATA_TUPLE) ) #define MULT_GENERIC_GET_TYPENAME(N, DATA_TUPLE) \ BOOST_PP_TUPLE_ELEM(0, MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE)) #define MULT_GENERIC_GET_EXPR( N, DATA_TUPLE ) \ BOOST_PP_TUPLE_ELEM(1, MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE)) #define MULT_GENERIC_LEVEL_REP1(z, N, DATA_TUPLE) \ MULT_GENERIC_GET_TYPENAME( N, DATA_TUPLE ) \ : \ BOOST_PP_TUPLE_ELEM(1, DATA_TUPLE) /*LEVEL_MACRO*/ ( \ BOOST_PP_TUPLE_ELEM(0, DATA_TUPLE) /*SEL_EXPR_SEQ*/ \ , BOOST_PP_SEQ_POP_FRONT( BOOST_PP_TUPLE_TO_SEQ(MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE)) ) \ ) #define MULT_GENERIC_LEVEL1(SEL_EXPR_SEQ, LEVEL_MACRO, ASSOC_SEQ) \ _Generic( \ (BOOST_PP_SEQ_HEAD(SEL_EXPR_SEQ)), \ BOOST_PP_ENUM( BOOST_PP_SEQ_SIZE(ASSOC_SEQ), MULT_GENERIC_LEVEL_REP1, (BOOST_PP_SEQ_POP_FRONT(SEL_EXPR_SEQ), LEVEL_MACRO, ASSOC_SEQ) ) \ ) #define MULT_GENERIC_LEVEL_REP2(z, N, DATA_TUPLE) \ MULT_GENERIC_GET_TYPENAME( N, DATA_TUPLE ) \ : \ BOOST_PP_TUPLE_ELEM(1, DATA_TUPLE) /*LEVEL_MACRO*/ ( \ BOOST_PP_TUPLE_ELEM(0, DATA_TUPLE) /*SEL_EXPR_SEQ*/ \ , BOOST_PP_SEQ_POP_FRONT( BOOST_PP_TUPLE_TO_SEQ(MULT_GENERIC_NTH_ASSOC_TUPLE(N, DATA_TUPLE)) ) \ ) #define MULT_GENERIC_LEVEL2(SEL_EXPR_SEQ, LEVEL_MACRO, ASSOC_SEQ) \ _Generic( \ (BOOST_PP_SEQ_HEAD(SEL_EXPR_SEQ)), \ BOOST_PP_ENUM( BOOST_PP_SEQ_SIZE(ASSOC_SEQ), MULT_GENERIC_LEVEL_REP2, (BOOST_PP_SEQ_POP_FRONT(SEL_EXPR_SEQ), LEVEL_MACRO, ASSOC_SEQ) ) \ ) #define MULT_GENERIC0(SEL_EXPR_SEQ, ASSOC_SEQ) \ BOOST_PP_SEQ_HEAD(ASSOC_SEQ) #define MULT_GENERIC1(SEL_EXPR_SEQ, ASSOC_SEQ) \ MULT_GENERIC_LEVEL1( SEL_EXPR_SEQ, MULT_GENERIC0, ASSOC_SEQ ) #define MULT_GENERIC2(SEL_EXPR_SEQ, ASSOC_SEQ) \ MULT_GENERIC_LEVEL2( SEL_EXPR_SEQ, MULT_GENERIC1, ASSOC_SEQ ) #define MULT_GENERIC(SEL_EXPR_TUPLE, ...) \ BOOST_PP_CAT(MULT_GENERIC, BOOST_PP_TUPLE_SIZE(SEL_EXPR_TUPLE)) ( BOOST_PP_TUPLE_TO_SEQ(SEL_EXPR_TUPLE), BOOST_PP_TUPLE_TO_SEQ((__VA_ARGS__)) )
我真的觉得上述解决方案并不比OP的原始实现更容易或更清晰。 我认为最好的方法是保持简单,只需要用更多的宏来抽象宏。 以下是一个例子。
#include double multiply_id ( int a, double b ) { return a * b; } double multiply_di ( double a, int b ) { return a * b; } double multiply_dd ( double a, double b ) { return a * b; } int multiply_ii ( int a, int b ) { return a * b; } /* #define multiply(a,b) _Generic((a), \ int: _Generic((b), \ int: multiply_ii, \ double: multiply_id), \ double: _Generic((b), \ int: multiply_di, \ double: multiply_dd) ) (a,b) */ #define _G2(ParamB,ParamA_Type, TypeB1, TypeB1_Func, TypeB2, TypeB2_Func) \ ParamA_Type: _Generic((ParamB), \ TypeB1: TypeB1_Func, \ TypeB2: TypeB2_Func) #define multiply(a,b) _Generic((a), \ _G2(b,int,int,multiply_ii,double,multiply_id), \ _G2(b,double,int,multiply_di,double,multiply_dd) ) (a,b) int main(int argc, const char * argv[]) { int i; double d; i = 5; d = 5.5; d = multiply( multiply(d, multiply(d,i) ) ,multiply(i,i) ); printf("%f\n", d); return 0; }
_G2
是两个参数generics的宏。 这可以很容易地扩展到_G3
或更多。 诀窍是正常做,然后从它的forms构建一个宏。