GCC vs CLANG:除了静态强制转换时在未使用的模板专用化中解析外,否

gcc vs clang: noexcept parsed in unused template specialization when static casting

本文关键字:专用 未使用 CLANG vs 静态 转换 GCC      更新时间:2023-10-16

我正在尝试将函数指针静态转换为特定函数重载,但似乎 clang 仍然解析(未使用的)模板专用化的 noexcept 语句,从而生成编译器错误。 GCC 似乎并不关心 no,除非相应的函数重载未使用。

template<typename T>
void fun( T ) noexcept( T(1) ){}
void fun(int) {}
void fun(int*) {}
int main () {
int a;
fun(&a); //calling works fine
fun(a);
static_cast<void(*)(int*)>(&fun); // static casting doesn't
}

https://godbolt.org/z/ixpl3f

哪个编译器在这里是错误的?
标准是否指定在将函数指针转换为特定重载时应编译的内容?

编辑: 在 Maxim 的评论之后,我将第二个 noexcept 放回了示例中,它在 gcc 和 clang 上编译。

所以这是另一个例子,它实际上失败了noexcept(noexcept(...))

https://godbolt.org/z/NMW99C

当涉及到获取函数模板的地址时,就像静态强制转换一样,标准有以下相关段落:

[over.over] 重载函数的地址(强调我的)

2 如果名称是函数模板,则进行模板参数推导([temp.deduct.funcaddr]),如果参数推导成功,则生成的模板参数列表用于生成单个函数模板专用化,该函数专用化将添加到所考虑的重载函数集中。

模板参数推导(不考虑异常规范)成功。因此,整个函数被实例化。只有在此实例化之后,编译器才会检查是否存在更好的非模板匹配:

4 如果选择了多个函数,则如果集合还包含不是函数模板专用化的函数,则会消除集合中的任何函数模板专用化

这与函数调用的情况不同,在函数调用中,重载解析将根据非模板重载的存在提前放弃函数专用化。在这种情况下,标准要求函数左值的存在,然后才能确定它确实需要。我不确定这是否可以被认为是措辞缺陷,或者有更高的原因,但它确实看起来不直观。

所以结论是Clang没有错,但GCC的行为更加直观。

[over.over] ¶1如果F(在可能应用函数指针转换 ([conv.fctptr])之后)与FT相同,则为上下文中所需的目标类型的函数类型FT选择类型为F的函数。

[conv.fctptr]类型为"noexcept指向函数的指针"的 prvalue 可以转换为"指向函数的指针"类型的 prvalue。

这意味着,在完成模板参数推导([over.over] ¶2)以确定要添加到重载集中的模板函数特化之后,模板函数专用化和非模板函数的类型必须都与目标类型进行比较,以决定应该"选择"其中的哪一个。

首选非模板函数 (¶4) 的决胜规则仅在两个函数的类型与目标类型匹配时生效(即,如果选择了多个函数)。

void(*)(int*)void(*)(int*)noexcept是两种不同的类型。强制转换表达式需要实例化两个重载的异常规范,以确定其中哪些与强制转换的目标类型匹配(即应选择其中的哪个),而调用表达式只需要实例化通过重载解析选择的重载的异常规范。

我相信 Clang 要求实例化函数模板专用化的异常规范是正确的。GCC 首先检查非模板函数的类型是否匹配是合理的,如果是,那么它就不需要检查函数模板专用化的类型,因为知道即使匹配,决胜局无论如何都会消除它。这可能也是一致的,这里的标准是模棱两可的。

有点跑题,但值得一提的是,如果你想说函数模板fun只有在T(1)不抛出时才noexcept,那么

template<typename T>
void fun( T ) noexcept( T(1) ){}

应该是

template<typename T>
void fun( T ) noexcept(noexcept(T(1))){}

第一个noexcept是说明符,第二个是运算符。