【问题标题】:Combining _Generic macros结合 _Generic 宏
【发布时间】:2016-02-27 14:30:37
【问题描述】:

我对 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,并以某种方式让客户端代码能够调用“函数”写入。默认看起来像一个向量,通过它可以实现这一点。

我在这个网站上能找到的最接近的匹配是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() 版本。我认为它需要在默认情况下调用现有的宏,但我不相信预处理器能够重命名现有的写入宏。如果可以的话,以下方法可以工作:

#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() 定义 |我需要解决上述任何一个问题。在默认情况下减少为无的 _Generic 子句将起作用,如果没有任何类型匹配,则将减少为无。

变得更加骇人听闻,如果函数采用指向结构的指针而不是结构的实例,并且我提供 write_void(void*x) {(void)x;} 作为默认选项,那么代码确实可以编译并运行。但是,将写入扩展为

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

本身显然很糟糕,而且我真的不想通过指针传递所有内容。

所以 - 任何人都可以看到一种增量定义单个 _Generic '函数' 的方法,即不从它将映射的所有类型的列表开始吗?谢谢。

【问题讨论】:

  • 需要跨多个不相关文件的类型泛型函数表明程序设计很差。这些文件要么是相关的,并且应该共享一个公共父级(“抽象基类”),然后可以在其中声明类型泛型宏和函数声明。或者它们不相关,但出于某种原因共享一些通用方法,在这种情况下,您需要发明一个通用的通用抽象层接口,然后它们可以实现。您应该始终首先考虑系统级别的程序设计。
  • 假设类型泛型函数是“相等”。通用接口对于所有 T 都是 bool equal(T lhs, T rhs)。你会建议什么共同的父母?我想到的通用抽象层是函数名'equal'。
  • 我发布了一个答案,它提出了一种完全不同的方法。

标签: c c11


【解决方案1】:

需要跨多个不相关文件的类型泛型函数表明程序设计很差。

这些文件要么是相关的,要么应该共享一个共同的父类(“抽象基类”),然后可以在其中声明类型泛型宏和函数声明。

或者它们是不相关的,但是出于某种原因共享一些通用方法,在这种情况下,您需要发明一个通用的通用抽象层接口,然后它们可以实现。您应该始终首先考虑系统级别的程序设计。

这个答案没有使用_Generic,而是提出了完全不同的程序设计。


以评论为例,bool equal(T lhs, T rhs)。也就是以上两种情况的后者,多个模块共享的通用接口。首先要注意的是,这是一个 functor,一个可以依次被通用算法(例如搜索/排序算法)使用的函数。 C 标准建议最好如何编写仿函数:

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

这是标准函数bsearchqsort 使用的格式。除非您有充分的理由,否则您不应该偏离该格式,因为如果您不这样做,您将免费获得搜索和排序。此外,这种形式的优点是可以在同一个函数中进行更少、更大和相等的检查。

在 C 中为此类函数实现通用接口的经典 C 方法是包含此宏的标头:

接口头:

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

实现标头的模块:

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

来电者:

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

这有抽象的好处:接口头文件不需要知道所有使用的类型。缺点是没有任何类型的安全性。

(但这是 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 成员。我们已经实现了多态性,因为特定类型的比较函数将被调用。

【讨论】:

  • “这有一个大问题:该宏必须提前知道所有现有类型” - 严格来说并非如此。我还没有一个优雅的解决方法,因为在预处理器中构建堆栈有点粗糙,但确实存在丑陋的解决方法。
  • 我很欣赏在 C 中实现类型擦除接口的速成课程,但我不认为从一个公共基础派生所有类型是一件好事。它与不是从所述基础派生的其他库不能很好地配合。将值类型与函数指针相关联,然后将它们存储在堆中也是一件荒谬的事情,尽管我接受“当然,首先编写一个 python 解释器”确实满足减少类型的明确要求。
【解决方案2】:

根据 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;
}

我真的希望有比这更好的方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-20
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多