C macro _Generic会出现意外的编译器错误

问题描述 投票:7回答:2

使用gcc.exe(Rev3,由MSYS2项目构建)8.2.0。

我试图构建一个宏来自动在两种类型之间进行类型转换,其中两个参数永远不应该是相同的类型。我的问题是如果我不包含相同类型的情况,编译器会抛出错误。我想要的:

#include <stdio.h>
#include <stdint.h>
// Macro to return string based on two different types
#define bob( to, from ) \
    _Generic( to , \
    int32_t: _Generic(from,  \
      int16_t: "s-l", \
      int8_t:  "c-l" ) , \
    int16_t: _Generic(from, \
      int32_t: "l-s", \
      int8_t:  "c-s") , \
    int8_t:_Generic(from, \
      int32_t: "l-c",  \
      int16_t: "s-c")  \
    )

    void main(void)
    {
        int32_t i1;
        int16_t s1;
        int8_t  c1;

        printf("%s\n", bob(i1,s1));
        printf("%s\n", bob(i1,c1));
        printf("%s\n", bob(s1,c1));
        printf("%s\n", bob(s1,i1));
        printf("%s\n", bob(c1,s1));
        printf("%s\n", bob(c1,s1));

    }

$ gcc gbug.c -o gbug.exe
gbug.c: In function 'main':
gbug.c:23:27: error: '_Generic' selector of type 'short int' is not compatible with any association
     printf("%s\n", bob(i1,s1));
                           ^~
gbug.c:9:19: note: in definition of macro 'bob'
 int16_t: _Generic(from, \
                   ^~~~
gbug.c:24:27: error: '_Generic' selector of type 'signed char' is not compatible with any association
     printf("%s\n", bob(i1,c1));
                           ^~
gbug.c:12:17: note: in definition of macro 'bob'
 int8_t:_Generic(from, \
                 ^~~~
gbug.c:25:27: error: '_Generic' selector of type 'signed char' is not compatible with any association
     printf("%s\n", bob(s1,c1));
                           ^~
gbug.c:12:17: note: in definition of macro 'bob'
 int8_t:_Generic(from, \
                 ^~~~
gbug.c:26:27: error: '_Generic' selector of type 'int' is not compatible with any association
     printf("%s\n", bob(s1,i1));
                           ^~
gbug.c:6:19: note: in definition of macro 'bob'
 int32_t: _Generic(from,  \
                   ^~~~
gbug.c:27:27: error: '_Generic' selector of type 'short int' is not compatible with any association
     printf("%s\n", bob(c1,s1));
                           ^~
gbug.c:9:19: note: in definition of macro 'bob'
 int16_t: _Generic(from, \
                   ^~~~
gbug.c:28:27: error: '_Generic' selector of type 'short int' is not compatible with any association
     printf("%s\n", bob(c1,s1));
                           ^~
gbug.c:9:19: note: in definition of macro 'bob'
 int16_t: _Generic(from, \

这个例子是我发现的最简单的失败。

如果我添加“相同类型”的转换行,如下所示:

#define bob( to, from ) \
_Generic( to , \
int32_t: _Generic(from,  \
  int16_t: "s-l", \
  int32_t: "bug", \
  int8_t: "c-l" ) , \
int16_t: _Generic(from, \
  int32_t: "l-s", \
  int16_t: "bug", \
  int8_t: "c-s") , \
int8_t:_Generic(from, \
  int32_t: "l-c",  \
  int8_t: "bug", \
  int16_t: "s-c")  \
)

它构建并运行预期的结果:

$ ./gbug.exe
s-l
c-l
c-s
l-s
s-c
s-c

验证我没有使用宏来扩展任何相同类型的条件。我理解_Generic不是一个字符串替换宏但我也认为如果你可以在没有默认情况下使用它,如果你使用了一个未知类型(或者一个不支持的类型组合,这就是我想要的行为,它会正确抛出编译错误) )就像预处理器将两个宏参数混淆了一样。

编辑:所以我有一个更好的理解,(请参阅下面的答案)但仍然希望得到宏,如果两个参数是相同的类型,则抛出编译错误。到目前为止,我有一个强制链接错误的技巧,这仍然比运行时错误更好。

c c-preprocessor c11
2个回答
6
投票

问题是泛型选择的每个分支必须是有效的,即使它们未被评估。

例如,你的第一个宏:

bob(i1, s1)

扩展为(为清晰起见而添加的类型):

_Generic( ((int32_t) i1),
  int32_t: _Generic( ((int16_t) s1),
    int16_t: "s-l",
    int8_t:  "c-l" ),
  int16_t: _Generic( ((int16_t) s1),  // The error is here
    int32_t: "l-s",
    int8_t:  "c-s"),
  int8_t:_Generic( ((int16_t) s1),
    int32_t: "l-c",
    int16_t: "s-c")
)

显然uint32_t分支是有效的:它只选择"s-l"。但int16_t分支无效,因为from(An int16_t本身)没有相应的分支。

在这种特殊情况下,添加一个什么都不做的自转换运算符也没什么坏处。


2
投票

A-Ha片刻,感谢John Bollinger的评论。

如果我手将宏扩展为代码:

void main(void)
{
    int32_t i1;
    int16_t s1;
    int8_t  c1;

    printf("%s\n", 
    _Generic( i1 , int32_t: _Generic(s1, int16_t: "s-l", int8_t: "c-l" ), 
                   int16_t: _Generic(s1, int32_t: "l-s", int8_t: "c-s" ),   // <-- No int16_t here
                   int8_t:  _Generic(s1, int32_t: "l-c", int16_t: "s-c") ) );

}

很明显,除非未采用的路径被删除,否则这将无法编译,我猜不会发生什么。

所以我猜错误情况的默认情况是正确的方法吗?

编辑:所以我仍然没有弄清楚如何获取默认情况下抛出编译器错误,但是我发现如果我在默认情况下调用一个不存在的函数它将编译但是如果我将抛出链接器错误违反我试图强制执行的规则。不是很好,但比运行时错误更好。

char *this_function_does_not_exist(); // fake function prototype
#define bob( to, from ) \
    _Generic( to , \
    int32_t: _Generic(from,  \
      default: this_function_does_not_exist(), \
      int16_t: "s-l", \
      int8_t:  "c-l" ) , \
    int16_t: _Generic(from, \
      default: this_function_does_not_exist(), \
      int32_t: "l-s", \
      int8_t:  "c-s") , \
    int8_t:_Generic(from, \
      default: this_function_does_not_exist(), \
      int32_t: "l-c",  \
      int16_t: "s-c")  \
    )

如果读取此内容的人有更好的,C11,实际上将_Static_assert嵌入到_Generic中让我知道。 (我知道我可以在_Static_assert中嵌入一个_Generic,它只是变得非常难看,而且我不想保持重复的逻辑)

© www.soinside.com 2019 - 2024. All rights reserved.