为什么 Clang 更喜欢主模板而不是 C++17 的特化?

Why does Clang prefer the primary template over the specialization from C++17?

以下program is reduced from the code in this question

template <typename T, void (*)(T), typename = void>  
struct S;

template <typename T, void (*f)(T)>
struct S<T, f, void> {};

S<int const, nullptr> s;

在所有版本的 GCC 中,在所有语言修订中,实例化 s 时选择 S 的特化。

在所有版本的 Clang 中,但仅从 C++17 开始,在实例化时选择主模板 s

我认为值得注意的几点是,如果实例化结束 <int, nullptr>,则永远不会选择主要参数,即第一个参数不再是 int const。此外,如果第二个参数中函数指针的签名不包含 T 作为参数,即如果第二个参数是 T (*)()void (*)(),则永远不会选择主节点,说。

如果此代码不是 IFNDR,那么哪个编译器是正确的? C++17 语言修订版是否有重大变化?

这是因为 C++17 允许从非类型参数的类型推导出模板类型参数。 [temp.deduct.type]/13:

When the value of the argument corresponding to a non-type template parameter P that is declared with a dependent type is deduced from an expression, the template parameters in the type of P are deduced from the type of the value.

因此,当我们尝试将 S<int const, nullptr> 与偏特化进行匹配时,我们从两个来源推导出偏特化的模板参数 T

  • 从第一个模板参数 int const,我们推导出 T = int const
  • 从第二个模板参数(其类型为 void (*)(int) 因为函数参数的顶级 cv 限定被调整掉),我们推导出 T = int.

由于我们推导了相互矛盾的结果,推导失败,偏特化不匹配。

早在 2019 年就在核心反射器上提出了类似的例子。人们一致认为这是标准中的一个缺陷,并且从非类型模板参数的类型中推导应该只发生在那些否则无法推导。