我正在学习C++17针对非类型模板参数的新功能decltype(auto)
。我写了一个简单的代码片段如下:
#include <type_traits>
template<decltype(auto) arg>
struct Foo {};
int
main()
{
constexpr int x = 42;
static_assert(std::is_same_v<Foo<42>, Foo<x>>);
}
据我所知,Foo<42>
应该是与Foo<x>
相同的类型。
然而,static_assert
语句使用clang++、MSVC 19.27编译,但使用GCC 10.2、MSVC 19.25失败。
我的问题是:为什么编译器的行为不同?标准对此有何规定?
编译器资源管理器链接:
叮当++https://godbolt.org/z/66M695
gcchttps://godbolt.org/z/3v5Mhd
MSVC 19.25https://godbolt.org/z/qP6v89
MSVC 19.27https://godbolt.org/z/14aK5Y
这都在描述decltype
如何工作的规则中。
[dcl.type.simple]
4对于表达式
e
,由decltype(e)
表示的类型为定义如下:
如果
e
是命名结构化绑定([dcl.struct.bind])的未加括号的id表达式,则decltype(e)
是中给定的引用类型结构化绑定声明的规范;否则,如果
e
是未加括号的id表达式或未加括号类成员访问,则decltype(e)
是以CCD_ 12命名的实体。如果没有这样的实体,或者如果e命名一个集合由于函数过载,程序格式不正确;否则,如果e是x值,则
decltype(e)
是T&&
,其中T
是e
的类型;否则,如果e是左值,则
decltype(e)
是T&
,其中T
是e
的类型;否则,
decltype(e)
是e
的类型。
使用decltype(auto)
时,e
是用作对象(arg
)初始值设定项的表达式。在OP中,此表达式为x
。它是一个未加括号的id表达式,因此decltype(x)
将是由x
命名的实体的类型。该类型是int const
,因为constexpr
说明符意味着const
。
[dcl.constexpr]
9对象声明中使用的
constexpr
说明符声明对象为CCD_ 33。此类对象应具有文字类型,并且被初始化。在任何constexpr
变量声明中初始化的完整表达式应为常量表达式。
因此,这里对您的代码示例进行了一个可爱的修改,使GCC接受它。
static_assert(std::is_same_v<Foo<42>, Foo<+x>>);
为什么?这是因为+x
不再是一个id表达式。它是int
类型的普通旧prvalue表达式,具有与x
相同的值。这就是decltype(auto)
所推导的,一个int
。
总而言之,拒绝您的代码的编译器操作正确。我想它会向您展示,使用decltype(auto)
作为非类型模板参数应该附带一个简短的免责声明。
在C++17中,由于以下规则,我认为GCC
是错误的:
temp.type#1
如果,两个模板id引用相同的类、函数或变量
1.1它们的模板名称、运算符函数ID或文字运算符ID引用相同的模板和
[…]
1.3它们对应的积分或枚举类型的非类型模板参数具有相同的值
形式上,名称用于指代实体
基本概念#5
每个表示实体的名称都由声明引入。
因此,无论是Foo<42>
还是Foo<x>
,它们的模板名称都引用了我们声明的实体template<decltype(auto) arg> struct Foo {};
。因此首先满足项目符号CCD_ 45。显然,在本例中,相应的模板参数具有相同的值,即42
。至少,根据c++17标准的说法,它们是等价类型。因此GCC
是错误的。此外,GCC 7.5同意这些类型是等效的。
然而,在最新的草案中有一些变化。它引入了一个新的措辞";模板参数等价">
温度类型#1
如果,两个模板ID相同
1.1它们的模板名称、运算符函数ID或文字运算符ID引用相同的模板,
1.2…
1.3它们对应的非类型模板参数在转换为模板参数的类型后是等效的模板参数(见下文)
和
模板参数等效
如果两个值属于相同类型且
它们是积分类型,并且它们的值是相同的
如其他答案所述,Foo<42>
的推导类型为int
。相反,推导出的Foo<x>
的类型是int const
。尽管它们的推导类型不同,但是,应该遵守这样一个规则:
在确定其类型时,会忽略模板参数上的顶级cv限定符。
因此after conversion to the type of the template-parameter
,这两个值的类型相同,因此它们是模板参数等效的。因此,在c++20标准下讨论这个例子,GCC
仍然是错误的。
我认为这是一个gcc错误,static_assert
应该通过。
据此:
如果模板参数的类型包含占位符类型,则推断出的参数类型是通过占位符类型推导从模板参数类型确定的。。。
占位符类型推导在这种情况下,意味着参数的类型是通过这些发明的声明推导出来的:
decltype(auto) t = 42; // for Foo<42> : int
decltype(auto) t = x; // for Foo<x> : int const
然后,根据这个:
非类型模板参数应具有以下类型之一(可选cv合格):
(4.6)包含占位符类型的类型。
在确定模板参数的类型时,会忽略该参数上的顶级cv限定符
由于在根据发明的声明确定类型时会忽略顶级限定符,因此Foo<42>
和Foo<x>
都应该具有相同的类型,并且static_assert
应该通过。