gcc vs clang:静态转换时在未使用的模板专业化中解析的 noexcept

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

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

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] Address of overloaded function (emphasis mine)

2 If the name is a function template, template argument deduction is done ([temp.deduct.funcaddr]), and if the argument deduction succeeds, the resulting template argument list is used to generate a single function template specialization, which is added to the set of overloaded functions considered.

模板参数推导(不考虑异常规范),成功。那么整个函数就被实例化了。只有在这个实例化之后,编译器才会检查是否有更好的 non-template 匹配:

4 If more than one function is selected, any function template specializations in the set are eliminated if the set also contains a function that is not a function template specialization

这与函数调用的情况不同,在函数调用中,重载决议将根据 non-template 重载的存在提前放弃函数特化。在这种情况下,标准在确定确实需要它之前需要函数左值的存在。我不确定它是否可以被视为措辞缺陷,或者有更高的原因,但它看起来确实 un-intuitive.

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

有点off-topic,但值得一提的是,如果您想说函数模板 fun 只有在 T(1) 不抛出时才为 noexcept,然后

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

应该是

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

第一个noexcept是一个specifier, the second one is an operator

[over.over] ¶1 A function with type F is selected for the function type FT of the target type required in the context if F (after possibly applying the function pointer conversion ([conv.fctptr])) is identical to FT.

[conv.fctptr] A prvalue of type "pointer to noexcept function" can be converted to a prvalue of type "pointer to function".

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

tie-breaker 优先使用 non-template 函数 (¶4) 仅当两个函数的类型都匹配目标类型时才会生效(即如果选择了多个功能)。

void(*)(int*)void(*)(int*)noexcept 是两种不同的类型。 cast 表达式需要实例化两个重载的异常规范,以确定它们中的哪一个与 cast 的目标类型匹配(即应该选择它们中的哪一个),而 call 表达式只需要实例化异常规范重载决议选择的重载。

我相信 Clang 要求实例化函数模板特化的异常规范是正确的。 GCC首先检查 non-template 函数的类型是否匹配是合理的,如果匹配,则不需要检查函数模板特化的类型,因为知道即使匹配, tie-breaker 无论如何都会消除它。这也可能是符合标准的,这里的标准不明确。