C语言 组合_通用宏

8nuwlpux  于 2023-11-16  发布在  其他
关注(0)|答案(4)|浏览(128)

我很高兴C11的_Generic机制--类型切换是我在C++中所缺少的。然而,它被证明是很难编写的。
例如,给定函数:

bool write_int(int);
bool write_foo(foo);
bool write_bar(bar);
// bool write_unknown is not implemented

字符串
我就可以写

#define write(X) _Generic((X), \
  int : write_int, \
  foo: write_foo, \
  bar: write_bar, \
  default: write_unknown)(X)


而且,如果我不尝试使用&write或将其传递给函数,我可以调用write(obj),如果obj是这些类型之一的示例,一切都很好。
然而,一般来说,foo和bar是完全无关的。它们在不同的头文件中定义,很少(但偶尔)在一个源文件中一起使用。那么扩展到_Generic的宏应该写在哪里?
目前,我正在积累一些头文件,比如write. h,equal. h,copy. h,move.h,每个头文件都包含一组函数原型和一个_Generic。这是可行的,但不是很好。我不喜欢在一个地方收集程序中每种类型的列表。
我希望能够在头文件中定义类型foo,沿着函数write_foo,并以某种方式使客户端代码能够调用“函数”write。默认值看起来像一个向量,通过它可以实现这一点。
我在这个网站上能找到的最接近的匹配是c11 generic adding types,它有一个部分的解决方案,但它还不足以让我看到如何合并的各种宏。
假设,在定义write_bar的头文件中,我们有一个现有的宏定义:

#define write(x) _Generic((x), bar: write_bar, default: some_magic_here)(x)


或者我们可以省略结尾的(x)

#define write_impl(x) _Generic((x), bar: write_bar, default: some_magic_here)


在这个标题的下面,我想要一个可以处理foo或bar的write()版本。我认为它需要在默认情况下调用现有的宏,但我不相信预处理器能够重命名现有的write宏。如果可以,下面可以工作:

#ifndef WRITE_3
#define WRITE_3(X) write(x)
#undef write(x)
#define write(x) __Generic((x),foo: write_foo,default: WRITE_3)(x)


我刚刚把它打出来,我可以看到一条前进的道路:

// In bar.h
#ifndef WRITE_1
#define WRITE_1(x) __Generic((x), bar: write_bar)
#elif !defined(WRITE_2)
#define WRITE_2(x) __Generic((x), bar: write_bar)
#elif !defined(WRITE_3)
#define WRITE_3(x) __Generic((x), bar: write_bar)
#endif
// In foo.h
#ifndef WRITE_1
#define WRITE_1(x) __Generic((x), foo: write_foo)
#elif !defined(WRITE_2)
#define WRITE_2(x) __Generic((x), foo: write_foo)
#elif !defined(WRITE_3)
#define WRITE_3(x) __Generic((x), foo: write_foo)
#endif
// In write.h, which unfortunately needs to be included after the other two
// but happily they can be included in either order
#ifdef WRITE_2
#define write(x) WRITE_1(x) WRITE_2(x) (x)
#elif
// etc
#endif


这实际上并不起作用,因为当x不匹配参数列表时,我找不到一种方法使WRITE_N(x)扩展为空。

controlling expression type 'struct foo' not compatible with any generic association type


expected expression // attempting to present an empty default clause


我认为在几个文件之间分发write()定义|宏我需要解决上面的任何一个问题。一个在默认情况下减少为nothing的_Generic子句可以工作,如果没有类型匹配,则减少为nothing的子句也可以工作。
如果函数接受指向结构体的指针而不是结构体的示例,并且我提供write_void(void*x){(void)x;}作为默认选项,那么代码将编译并运行。

write(x) => write_void(x); write_foo(x); write_void(x);


本身就很糟糕,而且我也不想用指针传递所有东西。
那么--有谁能想到一种方法来增量地定义一个_Generic 'function',也就是说,不需要从它将Map的所有类型的列表开始?谢谢。

cpjpxq1n

cpjpxq1n1#

需要跨多个不相关文件的类型泛型函数表明程序设计很差。
要么这些文件是相关的,并且应该共享一个公共的父类(“抽象基类”),然后可以在其中声明类型泛型宏和函数声明。
或者它们是不相关的,但出于某种原因共享一些共同的方法,在这种情况下,你需要发明一个共同的,通用的抽象层接口,然后他们可以实现。你应该总是考虑在系统级别上的程序设计,你做的第一件事。
这个答案没有使用_Generic,而是提出了一个完全不同的程序设计。
从注解中拿bool equal(T lhs, T rhs)做例子。这是上述两种情况中的后一种,多个模块共享的公共接口。首先要注意的是,这是一个 functor,一个可以反过来被泛型算法(如搜索/排序算法)使用的函数。C标准建议如何编写functor:

int compare (const void* p1, const void* p2)

字符串
这是标准函数bsearchqsort使用的格式。除非你有很好的理由,否则你不应该偏离这种格式,因为如果你不这样做,你将免费获得搜索和排序。此外,这种形式的优点是在同一个函数中执行较小,较大和相等的检查。
在C中实现这样一个函数的公共接口的经典C方法是包含以下宏的头文件:
接口标题:

#define compare(type, x, y) (compare_ ## type(x, y))


实现header的模块:

// int.c
int compare_int (const void* p1, const void* p2)
{
  return *(int*)p1 - *(int*)p2;
}


来电者:

if( compare(int, a, b) == 0 )
{
  // equal
}


这样做的优点是抽象:接口头文件不需要知道所有使用的类型,缺点是没有类型安全。
(But这是C语言,你不可能通过编译器获得100%的类型安全。如果你很担心的话,使用静态分析。)
在C11中,你可以通过引入一个_Generic宏来提高类型安全性。但这有一个很大的问题:这个宏必须事先知道所有现有的类型,所以你不能把它放在抽象接口头中。相反,它不应该放在公共头中,因为这样你会在每个类型之间创建一个紧密耦合,您可以在调用应用程序中创建这样一个宏,不是为了定义接口,而是为了确保类型安全。
相反,你可以做的是通过继承一个抽象基类来强制一个接口:

// interface.h
typedef int compare_t (const void* p1, const void* p2);

typedef struct data_t data_t; // incomplete type

typedef struct
{
  compare_t* compare;
  data_t*    data;
} interface_t;


继承接口的模块在创建对象时将比较函数指针设置为指向特定的比较函数。data是模块私有的,可以是任何东西。假设我们创建一个名为“xy”的模块,它继承了上面的接口:

//xy.c
struct data_t
{
  int x;
  int y;
};

static int compare_xy (const void* p1, const void* p2)
{
  // compare an xy object in some meaningful way
}

void xy_create (interface_t* inter, int x, int y)
{
  inter->data = malloc(sizeof(data_t));
  assert(inter->data != NULL);

  inter->compare = compare_xy;
  inter->data->x = x;
  inter->data->y = y;
}


然后调用者可以使用泛型interface_t并调用compare成员。我们已经实现了多态性,因为特定于类型的比较函数将被调用。

busg9geu

busg9geu2#

基于Leushenko对multiparameter generics的回答,我提出了以下可怕的解决方案。它要求参数通过指针传递,涉及的样板相当糟糕。它确实可以编译和运行,以允许函数返回值的方式。

// foo.h
#ifndef FOO
#define FOO
#include <stdio.h>
#include <stdbool.h>

struct foo
{
  int a;
};

static inline int write_foo(struct foo* f)
{
  (void)f;
  return printf("Writing foo\n");
}

#if !defined(WRITE_1)
#define WRITE_1
#define WRITE_PRED_1(x) _Generic((x), struct foo * : true, default : false)
#define WRITE_CALL_1(x)                         \
  _Generic((x), struct foo *                    \
           : write_foo((struct foo*)x), default \
           : write_foo((struct foo*)0))

#elif !defined(WRITE_2)
#define WRITE_2
#define WRITE_PRED_2(x) _Generic((x), struct foo * : true, default : false)
#define WRITE_CALL_2(x)                         \
  _Generic((x), struct foo *                    \
           : write_foo((struct foo*)x), default \
           : write_foo((struct foo*)0))
#elif !defined(WRITE_3)
#define WRITE_3
#define WRITE_PRED_3(x) _Generic((x), struct foo * : true, default : false)
#define WRITE_CALL_3(x)                         \
  _Generic((x), struct foo *                    \
           : write_foo((struct foo*)x), default \
           : write_foo((struct foo*)0))
#endif

#endif

// bar.h
#ifndef BAR
#define BAR
#include <stdio.h>
#include <stdbool.h>

struct bar
{
  int a;
};

static inline int write_bar(struct bar* b)
{
  (void)b;
  return printf("Writing bar\n");
}

#if !defined(WRITE_1)
#define WRITE_1
#define WRITE_PRED_1(x) _Generic((x), struct bar * : true, default : false)
#define WRITE_CALL_1(x)                         \
  _Generic((x), struct bar *                    \
           : write_bar((struct bar*)x), default \
           : write_bar((struct bar*)0))

#elif !defined(WRITE_2)
#define WRITE_2
#define WRITE_PRED_2(x) _Generic((x), struct bar * : true, default : false)
#define WRITE_CALL_2(x)                         \
  _Generic((x), struct bar *                    \
           : write_bar((struct bar*)x), default \
           : write_bar((struct bar*)0))
#elif !defined(WRITE_3)
#define WRITE_3
#define WRITE_PRED_3(x) _Generic((x), struct bar * : true, default : false)
#define WRITE_CALL_3(x)                         \
  _Generic((x), struct bar *                    \
           : write_bar((struct bar*)x), default \
           : write_bar((struct bar*)0))
#endif

#endif

// write.h
#ifndef WRITE
#define WRITE

#if defined(WRITE_3)
#define write(x)                                                        \
  WRITE_PRED_1(x) ? WRITE_CALL_1(x) : WRITE_PRED_2(x) ? WRITE_CALL_2(x) \
                                                      : WRITE_CALL_3(x)
#elif defined(WRITE_2)
#define write(x) WRITE_PRED_1(x) ? WRITE_CALL_1(x) : WRITE_CALL_2(x)

#elif defined(WRITE_1)
#define write(x) WRITE_CALL_1(x)
#else
#error "Write not defined"
#endif

#endif

// main.c
#include "foo.h"
#include "bar.h"

#include "write.h"

int main()
{
  struct foo f;
  struct bar b;

  int fi = write(&f);
  int bi = write(&b);

  return fi + bi;
}

字符串
我真的希望有比这更好的办法。

lokaqttq

lokaqttq3#

你可以做的一件事是:
在foo. h

#ifdef write
#undef write
#endif

#ifndef BAR_PLACEHOLDER
#define BAR_PLACEHOLDER
#endif

#define write(X) _Generic((X), \
  int: write_int, \
  foo: write_foo, \
  BAR_PLACEHOLDER \
  default: write_unknown)(X)

#define FOO_PLACEHOLDER \
foo: write_foo,

字符串
单位:bar. h

#ifdef write
#undef write
#endif

#ifndef FOO_PLACEHOLDER
#define FOO_PLACEHOLDER
#endif

#define write(X) _Generic((X), \
  int: write_int, \
  FOO_PLACEHOLDER \
  bar: write_bar, \
  default: write_unknown)(X)

#define BAR_PLACEHOLDER \
bar: write_bar,


这样任何包含其中一个的文件(以任何顺序)都可以调用marco write .而不需要write.h来查看所有类型

nue99wik

nue99wik4#

一般来说,你需要的是在宏中添加额外的元素。
一种解决方案是为多个宏创建“插槽”。这不会正确地扩展。但是,如果最大限制是静态的,则可以“附加”到宏。考虑以下内容:

// write_inc.h
#ifndef WRITE_COUNTER
#error include write.h first!

#elif WRITE_COUNTER == 1
#undef WRITE_COUNTER
#undef WRITE_ADDED
#define WRITE_COUNTER 2
#define WRITE_ADDED  \
     , _write_type_1: _write_callback_1

#elif WRITE_COUNTER == 2
#undef WRITE_COUNTER
#undef WRITE_ADDED
#define WRITE_COUNTER 3
#define WRITE_ADDED  \
     , _write_type_1: _write_callback_1 \
     , _write_type_2: _write_callback_2

#elif WRITE_COUNTER == 3
#undef WRITE_COUNTER
#undef WRITE_ADDED
#define WRITE_COUNTER 4
#define WRITE_ADDED  \
     , _write_type_1: _write_callback_1 \
     , _write_type_2: _write_callback_2 \
     , _write_type_3: _write_callback_3

/* etc... easy to generate with a script */
#else
#error too many iterations - add more
#endif

// write.h
#ifndef WRITE_H_
#define WRITE_H_
#include <stdbool.h>

#define CONCAT(a, b)   a ## b
#define CONCATX(a, b)  CONCAT(a, b)

// Initial values
#define WRITE_COUNTER  1
#define WRITE_ADDED    /* default empty */

// Add an overload of type: function.
#define WRITE_ADD(type, function) \
   typedef type CONCATX(_write_type_, WRITE_COUNTER); \
   static inline bool CONCATX(_write_callback_, WRITE_COUNTER)( \
           CONCATX(_write_type_, WRITE_COUNTER) _write_var \
   ) { \
        return (function)(_write_var); \
   }

bool write_int(int);

#define write(X)  \
    _Generic((X) \
    , int: write_int \
    WRITE_ADDED \
    )(X)

#endif

字符串
write_inc使用递增计数器,并使用用户指定的回调递增WRITE_ADDED的“过载”。
WRITE_ADD宏用于添加下一次迭代。由于WRITER_COUNTER将递增,因此它每次调用都会创建下一个_write_type_X_write_callback_X类型和宏。
它的用法如下:

// bar.h
#include "write.h"
#include <stdio.h>

struct bar { char bar; };
bool write_bar(struct bar) { printf("%s\n", __func__); }
WRITE_ADD(struct bar, write_bar)
#include "write_inc.h"

// foo.h
#include "write.h"
#include <stdio.h>

struct foo { char foo; };
bool write_foo(struct foo) { printf("%s\n", __func__); }
WRITE_ADD(struct foo, write_foo)
#include "write_inc.h"

// main.c
#include "bar.h"
#include "foo.h"
int main() {
   // clean use, and you can include bar.h or foo.h or both
   write((struct foo){0});
   write((struct bar){0});
}


系统是非常可扩展的,虽然WRITE_ADD不能处理所有情况,但它很容易使用。要使用复杂的类型,只需首先使用typedef创建别名,然后使用WRITE_ADD。使用GCC,WRITE_ADD可以使用typedef typeof(TYPE)__attribute__((__alias__(#function))))
还有godbolt link。

相关问题