在下一个示例程序中,具有非类型模板参数
decltype(auto)
的类模板部分专用于非类型模板参数 auto
:
template <decltype(auto)>
struct A { static const int v = 0; };
template <auto n>
struct A<n> { static const int v = 1; };
并且它被所有编译器接受,只有 GCC 发出警告
warning: partial specialization 'struct A<n>' is not more specialized than
note: primary template 'template<decltype(auto) <anonymous> > struct A'
以下断言是否正确(如 Clang 中所示)?
const int i = 0;
static_assert( A<i>::v == 1 ); //ok everywhere
static_assert( A<(i)>::v == 0 );//ok in Clang only
据我了解,这里Clang中的
A<(i)>
选择了参数类型为const int&
的主类模板,所以A<(i)>::v == 0
。而 Clang 中的 A<i>
选择参数类型为 int
的特化,因此 A<i>::v == 1
。
但是GCC和MSVC不同意,他们在这两种情况下都选择了专业化,所以
A<(i)>::v == 1
。在线演示:https://godbolt.org/z/Wfd8e1KPz
根据标准,这里哪个编译器是正确的?
我们必须按照 [temp.spec.partial.general]/9.2 的要求检查部分特化实际上是否比主模板更特化。 (据我所知,Clang 还没有实现这个规则,所以实际上,Clang 并没有对这个问题发表任何意见。)
[temp.spec.partial.order]解释了如何在两个部分专业化之间执行“更专业”的比较(我们必须将主模板视为也是部分专业化):我们必须将它们重写为函数模板每个都将类模板作为其唯一参数:
template <decltype(auto) x>
void f(A<x>); // (1)
template <auto x>
void f(A<x>); // (2)
然后我们必须应用 [temp.func.order] 来对这两个函数模板进行排序。第 3-4 段中解释的基本规则是,为了检查 (2) 是否比 (1) 更专业,我们必须为 (2) 中
auto
的 x
类型合成一个唯一类型和一个唯一值对于 x
本身,将其替换为该类型的 x
到声明 (2) 中,并尝试从结果函数类型中推导出 (1)。
但是我们有一个问题,因为实际上并不清楚代入(2)会产生什么(如果有的话)。让我们仔细阅读第3页:
为了生成转换后的模板,对于每个类型、非类型或模板模板参数(包括其模板参数包)分别合成一个唯一的类型、值或类模板,并将其替换为该参数的每次出现模板的函数类型.
在
void f(A<x>)
中,我们到底用什么来替代 x
?独特的价值。但这个值有什么特点呢?它是否是一个 id-expression(导致 decltype(auto)
推导出其声明的类型)?如果它不是 id-expression,那么它是左值还是纯右值?这会影响 decltype(auto)
是否被推导为引用类型或非引用类型。如果将其推导为引用类型,则适用其他限制,这可能会使替换的 template-id 无效。然后会发生什么?部分排序规则没说。
在这一点上,我确信我们在标准中的部分排序规范上存在严重问题,而且我们还不知道如何解决。对不起。我的建议是不要专门化具有
decltype(auto)
模板参数的模板。