C 宏 _Generic 给出了意外的编译器错误
C macro _Generic gives unexpected compiler error
使用 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, \
这个例子是我发现会失败的最简单的例子。
如果我像这样添加 "same type" 转换行:
#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 不是字符串替换宏,但我还认为,如果您可以在没有默认情况下使用它,如果您使用未知类型(或不受支持的类型组合,这是我想要的行为),它会正确地抛出编译错误)
就像预处理器混淆了两个宏参数。
编辑:所以我有了更好的理解(请参阅下面的回答),但如果两个参数类型相同,我仍然希望让宏抛出编译错误。到目前为止,我有一个技巧来强制 link 错误,这仍然比运行时错误更好。
问题是泛型选择的每个分支都必须有效,即使它们没有被评估。
例如,你的第一个宏:
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
(int16_t
本身)没有相应的分支。
在这种特殊情况下,添加一个什么也不做的自转换运算符不会有什么坏处。
A-Ha Moment,感谢 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,它变得非常丑陋,我不想维护重复的逻辑)
使用 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, \
这个例子是我发现会失败的最简单的例子。
如果我像这样添加 "same type" 转换行:
#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 不是字符串替换宏,但我还认为,如果您可以在没有默认情况下使用它,如果您使用未知类型(或不受支持的类型组合,这是我想要的行为),它会正确地抛出编译错误) 就像预处理器混淆了两个宏参数。
编辑:所以我有了更好的理解(请参阅下面的回答),但如果两个参数类型相同,我仍然希望让宏抛出编译错误。到目前为止,我有一个技巧来强制 link 错误,这仍然比运行时错误更好。
问题是泛型选择的每个分支都必须有效,即使它们没有被评估。
例如,你的第一个宏:
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
(int16_t
本身)没有相应的分支。
在这种特殊情况下,添加一个什么也不做的自转换运算符不会有什么坏处。
A-Ha Moment,感谢 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,它变得非常丑陋,我不想维护重复的逻辑)