C11 _Generic: 如何处理字符串字面量?

25

如何在C11中使用_Generic功能处理字符串字面量?

例如:

#include <stdio.h>
#define foo(x) _Generic((x), char *: puts(x))

int main()
{
    foo("Hello, world!");
    return 0;
}

在clang上会产生以下错误:

controlling expression type 'char [14]' not compatible with any generic association type

char *替换为char[]给我带来了

error: type 'char []' in generic association incomplete

据我所知,使这段代码能够编译的唯一方法有:

  1. 将字符串文字转换为适当的类型进行强制转换。这样做很丑陋,并且(在我看来)违反了使用 _Generic 的初衷。
  2. 使用 char[14] 作为类型指定符。你一定是在开玩笑吧...

我的假设是,当传递给 _Generic 的参数是数组时,它们会自动退化为指针,但显然不是这样。那么,我应该如何使用字符串文字与 _Generic 一起使用呢?这两个选项是唯一的选择吗?

我在 Debian 上使用的是 clang 3.2。不幸的是,这是我唯一可以访问支持此功能的编译器,因此我无法确定它是否是编译器错误。


5
请注意,即将发布的C17的N2176草案要求在6.5.1.1中的通用选择的控制表达式中进行数组到指针衰减,因此字符串字面值将被视为char*类型:"控制表达式的类型是作为lvalue转换(注:lvalue转换会丢弃类型限定符)、数组到指针转换或函数到指针转换的表达式类型。" - dpi
3个回答

21

这里是一个解决方案:

#include <stdio.h>
#define foo(x) _Generic((0,x), char*: puts(x))

int main()
{
    foo("Hello, world!");
    return 0;
}

这段代码编译并生成了以下结果:

$ clang t.c && ./a.out 
Hello, world!

有点笨拙,但我找不到更好的方法来使x衰减为指向字符的指针,也无法以你所要求的模糊方式匹配其类型,使用Apple LLVM版本4.2(clang-425.0.28)(基于LLVM 3.2svn)。

根据Jens Gustedt的这篇博客文章,GCC的行为是不同的(在GCC中,在_Generic上下文中,字符串自动衰减为指针,显然)。

顺便提一下,在C语言中,字符串字面值的类型是char数组,而不是const char。拒绝将char []作为type-namegeneric-association中是不是编译器bug:

一个通用选择不应该有超过一个默认的通用关联。通用关联中的类型名应指定一个非可变类型的完整对象类型。(6.5.1.1:2,我的重点)


谢谢,不错的建议,虽然有点烦人。关于 charconst char:是的,但 _Generic 模糊地添加了 const,所以我想利用这一点。我会编辑这个例子。干杯! - Michael Rawson
3
@MichaelRawson 在6.5.1.1中继续说道:“如果一个通用选择具有与控制表达式的类型兼容的类型名称的通用关联,则通用选择的结果表达式就是该通用关联中的表达式。”而const char *不兼容于char *。我不希望出现任何常量混淆,我的clang也不允许这样。 - Pascal Cuoq
非常抱歉,我可能错过了某个星号或其他东西。clang 的行为与您(以及标准)所描述的一样。 - Michael Rawson

15

我已经想出了一种避免使用巧妙的 (0,x) 技巧的方法。

如果你使用字符串字面值,类型是 char[s],其中 s 是字符串字面值的大小。

如何获取字符串字面值的大小?使用 sizeof 运算符:

#include <stdio.h>

#define Test( x )   _Generic( ( x ) ,   char*: puts ,                   \
                                        const char*: puts ,             \
                                        const char[sizeof( x )]: puts , \
                                        char[sizeof( x )]: puts )( x )

int main(void) 
{

    char str[] = "This" ;
    Test( str ) ;

    Test( "works" ) ;

    char str2[10] = "!!!" ;
    Test( str2 ) ;

return 0;
}

我尝试使用clang和Pelles编译它,它可以工作。

你仍然需要强制转换变量长度数组,这是唯一的问题。

经过进一步尝试,我发现了另一种类似于Pascal Cuoq所做的方法,使用&*运算符:

#include <stdio.h>
#define foo(x) _Generic( ( &*(x) ), char*: puts , const char*: puts )( x )

int main()
{
    foo("Hello, world!");
    return 0;
}

5
&* 是另一个“什么也不做”的上下文,就像 0, 一样强制指针衰减,但如果 x 不是指针(比如你想同时使用 _Generic 处理浮点数和字符串字面量),它就无法工作。不过我对你使用 sizeof 的方式印象深刻。 - Pascal Cuoq
@PascalCuoq 你是对的,(0,x) 是唯一的方法。我之所以这样做,只是因为编译器如果我使用 (0,x) 会给我一个警告:移除没有效果的表达式 - 2501
1
如果 _Generic 前面的语句是一个表达式,你可以将它移动到 _Generic 中,将 printf("hello world\n"); _Generic(x, …) 转换为 _Generic((printf("hello world\n"), x), …)。你也可以尝试使用 (((void)0), x) 来让编译器更清楚地理解你的意图。 - Pascal Cuoq
@PascalCuoq 嗯,( void )0 就是它了。我也在 clang online http://gcc.godbolt.org/ 上尝试了你的方法,但出现了错误,不过稍微改一下表达式:_Generic( ( (0,x)+0 ) 就解决了问题。 - 2501
对于所有类型,包括非指针类型,*&操作符都能正确工作吗? - psprint

6

3.7.1之前,Clang的行为不正确(C11缺陷报告481)。它在2016年3月8日发布的Clang 3.8.0中得到了修复。

委员会对DR 481的回应如下:

本文引发了长时间而富有成效的讨论。委员会同意_Generic提案的作者的观点,即明确避免选择限定类型以及按大小选择数组_Generic的目的是为C语言提供一种表达C++中“重载函数”概念的机制,特别是为实现者提供了一种可能的机制,用于实现第7.17.7节中的原子类型通用函数。


谢谢你的推荐。很遗憾他们改变了它。如果能像@2501建议的那样使用sizeof进行匹配,那将会非常棒。因为这样可以传递数组的大小,有效地防止指针衰减和所有相关的潜在错误。 - Born2Smile

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接