首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C11中多参数C函数的泛型

C11中多参数C函数的泛型
EN

Stack Overflow用户
提问于 2013-06-26 00:40:50
回答 5查看 8.2K关注 0票数 21

我理解单参数函数的C11泛型,如下所示:(来自here)

代码语言:javascript
复制
#define acos(X) _Generic((X), \
    long double complex: cacosl, \
    double complex: cacos, \
    float complex: cacosf, \
    long double: acosl, \
    float: acosf, \
    default: acos \
    )(X)

但是,对于有两个参数的函数来说,这似乎是一件痛苦的事情,你需要嵌套对_Generic的调用,这真的很难看;摘自同一篇博客:

代码语言:javascript
复制
#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)

有没有办法为多参数函数提供更多人类可读的泛型,例如:

代码语言:javascript
复制
#define plop(a,b) _Generic((a,b), \
      (int,long): plopii, \
      (double,short int): plopdd)(a,b)

提前感谢您的回复。基本的想法是为_Generic提供一个宏包装器。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-06-26 01:31:52

考虑到_Generic的控制表达式没有求值,我建议应用一些算术运算来进行适当的类型组合,并切换结果。因此:

代码语言:javascript
复制
#define OP(x, y) _Generic((x) + (y), \
    long double complex: LDC_OP(x, y), \
    double complex: DC_OP(x, y), \
    ... )

当然,这只适用于某些情况,但您始终可以展开那些“折叠”类型没有帮助的情况。(这样就可以处理N- of -char和char *,就像链接的printnl示例一样,然后如果组合的类型是int,就可以返回并检查charshort。)

票数 16
EN

Stack Overflow用户

发布于 2013-06-26 02:29:03

因为C没有元组,所以让我们创建自己的元组:

代码语言:javascript
复制
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 )

这是客户端代码:

代码语言:javascript
复制
#include <stdio.h>
#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;
}

而且它是有效的:

代码语言:javascript
复制
~/workspace$ clang -Wall -std=c11 temp.c
~/workspace$ ./a.out 
int, int
int, double
double, double
double, int

是的,您仍然需要以指数大小编写代码。但至少你可以重用它。

票数 14
EN

Stack Overflow用户

发布于 2014-09-08 06:50:38

这里有一个版本,它只需要你手工编写线性数量的代码,所有这些都与手头的事情直接相关(没有大量手工定义的类型)。首先,使用示例:

代码语言:javascript
复制
#include <stdio.h>

// 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

  • argument类型必须是一个单词的名称(由于

  • ,这不是一个大问题,但是需要是一个在实际调用点导致巨大代码膨胀的库)(尽管对于编译器来说,优化它很容易膨胀-在-O1)

  • relies的 PP
  • 上(见下文)

您还必须定义一个不带参数的X_default函数;不要将其添加到重载声明块中。这用于非匹配(如果您想直接调用它,可以使用任何非匹配值调用重载,比如复合文字匿名结构或其他东西)。

这是activate-overloads.h

代码语言:javascript
复制
// activate-overloads.h
#include <order/interpreter.h>

#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 preprocessor library了。

它的实际工作原理:OVERLOAD_ARG_TYPES声明用于构建一个枚举,将所有使用中的参数类型作为常量列出。然后,在调用者代码中,对重载名称的每个调用都可以替换为在所有实现(具有正确的参数编号)之间调度的大型三元操作。分派的工作方式是使用_Generic从参数的类型生成枚举值,将这些值放入一个数组中,并让自动生成的dispatcher函数返回该类型组合的ID (在原始块中的位置)。如果ID匹配,则调用该函数。如果参数的类型错误,则会为未使用的实现调用生成虚拟值,以避免类型不匹配。

从技术上讲,这涉及“运行时”分派,但由于每个类型ID都是常量,并且分派器函数是static inline,因此编译器应该很容易优化出所有事情,除了想要的调用(而GCC确实将其全部优化掉)。

这是对之前发布的here的技术的改进(相同的想法,现在具有漂亮和超轻量级的语法)。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/17302913

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档