推导的(非类型)模板参数类型的编译器差异

Compiler variance for the type of deduced (non-type) template parameter

考虑以下示例:

#include <type_traits>

struct A {};

template <A const i> void f() { 
    static_assert(std::is_same_v<decltype(i), A const>);  // #1
    A& ar = i;                                            // #2
}

int main() {    
    f<A{}>();
}

Clang(1) 和 GCC(1) 都拒绝了以下两个看似相互矛盾的错误:

#1 error: static_assert failed due to requirement 'std::is_same_v<A, const A>'
#2 error: binding reference of type 'A' to value of type 'const A' drops 'const' qualifier

GCC demo, Clang demo.

另外,如果我们将非类型模板参数的类型改为占位符类型,如下:

template <auto const i> void g() { 
    static_assert(std::is_same_v<decltype(i), A const>);  // #1
    A& ar = i;                                            // #2
}

然后 GCC 接受 #1,而 Clang 拒绝它(都拒绝 #2 如上所述)。

GCC demo, Clang demo.

这是怎么回事,哪个编译器是正确的(如果有的话)?


(1) GCC HEAD 11.0.0 20210117 和 Clang HEAD 12.0.0 (20210118),-std=c++20.

对于 f.

,GCC 和 Clang 都(可能)正确地为 #1#2 发出错误

根据[temp.param]/6[强调我的]:

A non-type template-parameter shall have one of the following (possibly cv-qualified) types:

  • (6.1) a structural type (see below),
  • (6.2) a type that contains a placeholder type ([dcl.spec.auto]), or
  • (6.3) a placeholder for a deduced class type ([dcl.type.class.deduct]).

The top-level cv-qualifiers on the template-parameter are ignored when determining its type.

顶级 cv 限定符在确定其类型时被忽略 ;与处理需要推导的占位符类型的 (6.2) 和 (6.3) 相比,(6.1) 的“确定”指的是什么可以说有些模糊。

[dcl.type.decltype]/1[强调我的]:

For an expression E, the type denoted by decltype(E) is defined as follows:

  • [...]
  • otherwise, if E is an unparenthesized id-expression naming a non-type template-parameter, decltype(E) is the type of the template-parameter after performing any necessary type deduction ([dcl.spec.auto], [dcl.type.class.deduct]);
  • [...]

提到fdecltype(i)的类型是非类型模板参数的类型,在进行any必要的类型推导后,紧接着通过连接到 [temp.param]/6.2 和 [temp.param]/6.3.

的两个引用

因此 f#1 的关键是 [temp.param]/6.1 是否也经历了“确定其类型”,即使它被明确注释为 A const(不需要扣除)。 GCC 和 Clang 似乎都同意这种情况:A const 经历“确定类型”并且 const 被删除。


f中理解#2更直接;与非类型模板参数(class 类型)关联的实际 模板参数对象 A 不一定与非类型模板参数相同;相反,它的类型由 [temp.param]/8 [emphasis mine]:

控制

An id-expression naming a non-type template-parameter of class type T denotes a static storage duration object of type const T, known as a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template-parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object. A template parameter object shall have constant destruction ([expr.const]). [ Note: If an id-expression names a non-type non-reference template-parameter, then it is a prvalue if it has non-class type. Otherwise, if it is of class type T, it is an lvalue and has type const T ([expr.prim.id.unqual]). — end note ]

使得任何 class 类型 T 模板参数对象 始终具有类型 const T,从而解释错误#2。请注意,后者仅适用于 class 类型,因为命名非类型非引用 template_parameter 的 id 表达式是纯右值。


至于 g#1 处 GCC 和 Clang 之间的差异,在应用 [temp.param]/6 时没有含糊之处,尤其是 [temp.param] /6.2,应用类型推导,同时忽略模板参数上的 cv 限定符。意思是 decltype(i)g 中的 A,GCC 接受行 #1 是错误的。我相应地提交了以下 GCC 错误报告: