组合 _Generic 宏

2024-01-14

我对 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 泛型添加类型 https://stackoverflow.com/questions/9734982/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

Or

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“函数”的方法,即无需从它将映射的所有类型的列表开始吗?谢谢。


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

这些文件是相关的,并且应该共享一个公共父级(“抽象基类”),然后可以在其中声明类型通用宏和函数声明。

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

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


以评论中的示例为例,bool equal(T lhs, T rhs)。那就是上面两种情况中的后者,一个公共接口被多个模块共享。首先要观察的是,这是一个functor,一个可以被搜索/排序算法等通用算法轮流使用的函数。 C 标准建议最好如何编写函子:

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

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

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

Caller:

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

这样做的优点是抽象:接口头文件不需要知道所有使用的类型。缺点是根本不存在类型安全。

(但这是 C,你永远无法通过编译器获得 100% 的类型安全。如果这是一个大问题,请使用静态分析。)

使用 C11,您可以通过引入_Generic宏。但这有一个大问题:该宏必须提前了解所有现有类型,因此您不能将其放入抽象接口标头中。相反,应该not位于公共标头中,因为这样您将使用该标头在每个不相关的模块之间创建紧密耦合。您可以在调用应用程序中创建这样的宏,不是为了定义接口,而是为了确保类型安全。


相反,您可以做的是通过继承抽象基类来强制执行接口:

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

组合 _Generic 宏 的相关文章

随机推荐

  • 如何恢复 HEAD^ 的树?

    tl dr 可以恢复吗HEAD 的树 如果它被删除并且没有事先推送并且其他一切都完好无损 我不小心删除了我的一部分 git 我不完全确定缺少什么 当发现git push没用 我跑了git fsck Checking object direc
  • 转换例如。 2012-05-25 转换为 1970 年 1 月 1 日以来的秒数

    我有一个数据库 可以节省时间time 来自 php 几秒钟后1 jan 1970 有什么办法可以转换 例如2012 12 12至秒后1 jan 1970 我想做这样的事情 SELECT FROM Table WHERE date gt 20
  • Fortran 语言中的睡眠

    有谁知道 Fortran 中休眠指定毫秒数的方法吗 我不想使用不可移植的系统调用 因此 Fortran 或 C 库固有的任何内容都是首选 使用Fortran ISO C Binding使用C库sleep以秒为单位进行睡眠 module Fo
  • 获取用户 Facebook 权限

    我想为我的用户添加 Facebook 权限 我就是这样做的 Session openActiveSession this true new Session StatusCallback Override public void call S
  • 如何将多个视图模型绑定到 MVVM wpf 中的单个 xaml 文件?

    我在 Mainwindow xaml 中有三个网格 每个网格中有多个控件 对于每个网格 我创建了一个单独的视图模型 但在 Mainwindow xaml 中 我可以将 DataContext 属性仅设置为其中一个类 这样只能将数据绑定到其中
  • 如何解决Windows 7上WAMP和Skype冲突? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 这个问题似乎不是关于主要由程序员使用的特定编程问题 软件算法或软件工具 help on topic 如果您认为该问题与主题相关另一个 St
  • 为什么需要 OleDbCommand.Prepare()?

    我正在使用一个数据网格和适配器 它们通过存储的查询 名为 UpdatePaid 3 个参数如下所示 与 MSAccess 表相对应 如下所示 OleDbCommand odc new OleDbCommand UpdatePaid conn
  • 如何在 Flutter 插件中添加 OpenCV

    我正在开发一个颤振项目 我必须检测对象的边缘 我搜索了现有的插件但没有找到 我打算使用 OpenCV 进行边缘检测 我从 Android Studio 创建了一个 flutter 插件 这是插件结构 我已经从这里下载了 Android 版
  • 错误!服务器退出而不更新PID文件(/usr/local/var/mysql/`username`.lan.pid)

    我无法摆脱下面的错误 通过安装自制程序 usr bin ruby e curl fsSL https raw githubusercontent com Homebrew install master install brew instal
  • 无法让“npm install -g”在任何包上运行(AppData/Roaming/npm 始终为空)

    在工作中在 Windows 7 Enterprise 上运行 nodejs 每当我安装需要 g 访问权限的 node module 时 根据经验 我知道它应该在中创建一个 bat 文件 AppData Roaming npm 但由于某种原因
  • 纬度/经度与 1 英里的换算是多少 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我正在使用 Google 地图 并且
  • 增加浅克隆的深度而不获取其他分支

    为了节省磁盘空间 我通常进行浅克隆 最新的远程git标签 https stackoverflow com q 29780641 5353461 默认分支 git clone depth 20 shallow submodules recur
  • 如何将我的node_module、模块添加到package.json中

    我的 node module 文件夹中有一些模块 但因为我是业余爱好者nodejs 当我想安装主题时 我忘记使用 save with npm install 现在我有很多模块但是我的package json是空的所以有什么办法可以添加主题到
  • 从 .NET 中的字符串获取 URL 参数

    我在 NET 中有一个字符串 它实际上是一个 URL 我想要一种简单的方法来从特定参数获取值 通常 我只会使用Request Params theThingIWant 但该字符串不是来自请求 我可以创建一个新的Uri像这样的项目 Uri m
  • 基于右 Kan 扩展的列表

    在 用于程序优化的 Kan 扩展 http www cs ox ac uk ralf hinze Kan pdfRalf Hinze 所著的列表类型的定义基于来自幺半群范畴的健忘函子的右 Kan 扩展 第 7 4 节 论文给出Haskell
  • Android Context.bindService 总是返回 false 并且 ServiceConnection 对象永远不会被触发

    我已遵循本地服务示例 http developer android com reference android app Service html LocalServiceSample由 Google 提供 但我的Context bindSe
  • Python IRC 机器人问题

    我想制作一个机器人 它允许我从 irc 定义函数 并将我的机器人视为 Python 解释器 例如 我想制作一个可以执行以下操作的机器人
  • 尝试从批处理文件中查找并加载信息后收到错误

    我一直在批量进行一些实验 目前正在开始一个小型的文本冒险 但是当我测试保存游戏时 程序返回错误 系统找不到指定的文件 有人可以帮我吗 如果有帮助的话 我在学校电脑上 这是我的相关代码片段 pushd schoolname MyName My
  • 使用 python 进行字数统计

    我用Python写了一个字数统计代码 我想从以下页面获取每个单词的文本和频率 问题是我的程序给了我除以每节经文的字数 但我希望它不除 请帮助我 import requests from bs4 import BeautifulSoup im
  • 组合 _Generic 宏

    我对 C11 的 Generic 机制感到很高兴 类型切换是我怀念 C 的功能 然而 事实证明它很难创作 例如 给定函数 bool write int int bool write foo foo bool write bar bar bo