非类型模板参数推导中的类型冲突

use*_*522 13 c++ language-lawyer template-argument-deduction non-type-template-parameter

#include<type_traits>

template <typename T, T>
struct A { };

template <typename T, T t>
void f(A<T, t>) {
}

int main() {
    f(A<const int, 0>{});
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/n6bcj5rjM


该程序在 C++14 和 C++17 模式下被 GCC 和 ICC 接受,在 C++14 模式下被 Clang 接受,但在 C++17 模式下被 Clang 拒绝,在任一模式下被 MSVC 拒绝。


拒绝是通过这样的诊断:

<source>:12:5: error: no matching function for call to 'f'
    f(A<const int, 0>{});
    ^
<source>:7:6: note: candidate template ignored: deduced conflicting types for parameter 'T' ('const int' vs. 'int')
void f(A<T, t>) {
     ^
Run Code Online (Sandbox Code Playgroud)

还要考虑以下变化:

const int i = 0;

int main() {
    f(A<const int&, i>{});
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/oa3xfv4jx

const int&相同的编译器仍然接受/拒绝,而那些拒绝的编译器现在抱怨和之间的类型不匹配int


哪些编译器是正确的?


我希望答案取决于它是在 C++17 之前还是之后,因为 C++17 在非类型模板参数推导中引入了(破坏性)更改,请参阅[diff.cpp14.temp]

特别是对于 C++17 及更高版本,我想知道T从参数 for 的推导是否t应该推导模板参数的类型,这将int在第一个变体中(因为顶级const被忽略)和const int&在第二个变体中,或者是否应该应用通常的表达式调整来进行推导auto ... = t;,在这种情况下,第二个变体应该推导Tint

如果这些类型与显式提供的类型不匹配,那么模板参数推导是否真的会失败T

use*_*570 1

下面给出的解释是针对第一个片段的(也如下所示):

\n
//----------------------v--->i named the parameter for explanation purposes\ntemplate <typename T, T p>\nstruct A { };\n\ntemplate <typename T, T t>\nvoid f(A<T, t>) {\n}\n\nint main() {\n    f(A<const int, 0>{});\n}\n
Run Code Online (Sandbox Code Playgroud)\n

步骤1

\n

这里我们考虑表达式会发生什么 A<const int, 0>{}

\n

来自temp.param#5

\n
\n

确定其类型时,模板参数上的顶级 cv 限定符将被忽略。

\n
\n

乍一看,当应用于给定的代码片段时,命名的非类型模板参数p应该是int而不是const int

\n

但请注意上面引用的声明中强调的突出部分。特别是,我对此(短语“确定时”)的解释是,在推导模板参数时会删除顶级 cv 限定符,因此在显式指定模板参数时不会删除顶级 cv 限定符

\n

由于在我给定的代码片段中,我们显式指定了模板参数,因此这里没有模板参数推导(TAD)。因此,不会发生由于上面引用的temp.param#5而导致顶级 cv 限定符被删除的情况。意思是 is的类型pconst int而不是int.

\n

请注意,我在解释上面的 [temp.param#5] 时可能是错误的。

\n
\n

但在进一步分析之前,来自temp.param#6

\n
\n

非类型非引用模板参数是纯右值。它不得被分配或以任何其他方式改变其价值。

\n
\n

此外,从expr#6

\n
\n

如果纯右值最初的类型为 \xe2\x80\x9ccv T\xe2\x80\x9d,其中 T 是 cv 未限定的非类、非数组类型,则在任何进一步的操作之前,表达式的类型将调整为T分析

\n
\n

当将temp.param#6expr#6应用于给定的代码片段时,这意味着p(纯右值)最终将是类型int而不是const int

\n

因此,非类型模板参数的p类型为int

\n

第2步

\n

接下来,我们考虑调用表达式 会发生什么f(A<const int, 0>{})

\n

现在,我们有一个纯右值A<const int, 0>,其第一个类型参数Tconst int,而第二个非类型模板参数p的类型为int。因此,对于函数模板,f<>第一个模板类型参数T被推导为const int,而非类型模板参数的类型t被推导为int。因此,类型之间存在冲突T,因此提到的错误说:

\n
<source>:7:6: note: candidate template ignored: deduced conflicting types for parameter \'T\' (\'const int\' vs. \'int\')\nvoid f(A<T, t>) {\n     ^\n
Run Code Online (Sandbox Code Playgroud)\n