[英]Using decltype(auto) in C++ non-type template parameter
我正在为非类型模板参数学习 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
gcc https://godbolt.org/z/3v5Mhd
MSVC 19.25 https://godbolt.org/z/qP6v89
MSVC 19.27 https://godbolt.org/z/14aK5Y
这一切都在描述decltype
工作原理的规则中。
[dcl.type.simple]
4对于表达式
e
,由decltype(e)
表示的类型定义如下:
如果
e
是命名结构化绑定 ([dcl.struct.bind]) 的无括号 id 表达式,则decltype(e)
是结构化绑定声明规范中给出的引用类型;否则,如果
e
是无括号的 id 表达式或无括号的类成员访问,则decltype(e)
是由e
命名的实体的类型。 如果没有这样的实体,或者如果 e 命名了一组重载函数,则程序格式错误;否则,如果 e 是 xvalue,则
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
说明符将对象声明为const
。 这样的对象应具有文字类型并应进行初始化。 在任何constexpr
变量声明中,初始化的完整表达式应为常量表达式。
所以这是对您的代码示例的一个可爱的修改,使 GCC 接受它。
static_assert(std::is_same_v<Foo<42>, Foo<+x>>);
这是为什么? 这是因为+x
不再是 id 表达式。 它是一个类型为int
的普通纯右值表达式,与x
具有相同的值。 所以这就是decltype(auto)
推导出的int
。
总而言之,拒绝您代码的编译器的行为是正确的。 我想它会告诉你,对非类型模板参数使用decltype(auto)
应该附带一个简短的免责声明。
在 C++17 中,由于以下规则,我认为GCC
是错误的:
温度类型#1
两个模板 ID 引用同一个类、函数或变量,如果
1.1 它们的template-names 、 operator-function-ids 或literal-operator-ids 指的是同一个模板并且
[...]
1.3 它们对应的整数或枚举类型的非类型模板参数具有相同的值
正式地,名称用于指代实体
基本概念#5
每个表示实体的名称都由声明引入。
因此,无论是Foo<42>
还是Foo<x>
,它们的模板名称都指向实体template<decltype(auto) arg> struct Foo {};
我们宣布。 所以首先满足子弹1.1
。 显然,在这个例子中,对应的模板参数具有相同的值,即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
应该通过。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.