类模板部分专用化:编译器错误

Class template partial specialization: compiler error

本文关键字:编译器 错误 专用 板部      更新时间:2023-10-16

这个程序

#include <iostream>
template <int I>
struct A
{
A() { std::cout << "A<I>()n"; }
};
template <int I>
struct A<I + 5>
{
A() { std::cout << "A<I + 5>()n"; }
};

int main()
{
return 0;
}

既不是由 gcc HEAD 10.0.020190 编译,也不是由 clang HEAD 10.0.0 编译。

例如 gcc 编译器发出错误

prog.cc:10:8: error: template argument '(I + 5)' involves template parameter(s)
10 | struct A<I + 5>
|        ^~~~~~~~

是否存在错误的类模板部分专用化?

这不是有效的部分特化(尽管错误可能会更好(。我们不遵守的条款如下:

[温度.class规格]

8 类模板部分的参数列表内 专业化,以下限制适用:

  • 专业化应比主要模板更专业。

"专业化不是更专业!?"我想你会想的。但事实确实如此。确定哪个"更专业"的规则在[temp.class.order]中描述。它的要点是我们要考虑两个假设的函数模板重载:

template <int I>
struct A { /* ... */ };
template<int I>
void foo(A<I>); //1
template<int I>
void foo(A<I + 5>); //2

然后,我们对函数模板执行重载解析和部分排序。如果#2获胜,它更专业,您的声明是合法的。如果未获胜,则声明无效。部分排序是通过制作一些参数并执行一个模板对另一个模板的模板参数推导来完成的。因此,假设我们首先比较第一个和第二个(为简单起见,我将重命名它们,但它们仍然是重载(:

void foo1(A<0>); -> void foo2(A<I + 5>);

论点演绎在这里成功吗?其实不然。I + 5是一个非推导的上下文:

[温度扣除类型]

非推导上下文是:

5.3 - 非类型模板参数或数组绑定 子表达式引用模板参数。

I引用模板参数,因此I + 5是非推导上下文。因此,模板参数推导在这个方向上失败。

让我们尝试另一个方向。我们再次制造一个论点

void foo2(A<1 + 5>); = void foo2(A<6>);  -> void foo1(A<I>); 

演绎显然在这里成功了。所以根据函数模板部分排序的规则,foo1foo2更专业。这意味着我们的初级实际上比我们的"部分专业化"更专业,使得部分专业化形成错误。

查看 temp.class.spec.match/3,我们有

如果由于模板参数

列表模板 id的结构而无法推断出部分专用化的模板参数,则程序格式不正确。

以示例

template <int I, int J> struct A {};
template <int I> struct A<I+5, I*2> {};     // error
template <int I> struct A<I, I> {};         // OK
template <int I, int J, int K> struct B {};
template <int I> struct B<I, I*2, 2> {};    // OK

Clang的错误消息与此相呼应:

error: class template partial specialization contains a template parameter that cannot be deduced; this partial specialization will never be used

(请参阅@StoryTeller的答案,了解为什么此推论在您的代码中失败,我不会在这里重复它。