mib*_*ibu 23 c++ language-lawyer c++17 decltype-auto
我正在学习 C++17decltype(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>>);
}
Run Code Online (Sandbox Code Playgroud)
据我了解,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
Sto*_*ica 15
这一切都在描述如何decltype工作的规则中。
[dcl.type.simple]
4对于表达式
e, 表示的类型decltype(e)定义如下:
if
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>>);
Run Code Online (Sandbox Code Playgroud)
这是为什么?这是因为+x不再是 id 表达式。它是 type 的普通旧纯右值表达式int,具有与x. 所以这就是decltype(auto)推论,一个int.
总而言之,拒绝您代码的编译器的行为是正确的。我想它会向您展示,decltype(auto)用于非类型模板参数应该附带一个简短的免责声明。
在 C++17 中,GCC由于以下规则,我认为是错误的:
temp.type#1
两个模板 ID 引用同一个类、函数或变量,如果
1.1 它们的template-names、operator-function-ids 或literal-operator-ids 引用相同的模板和
[...]
1.3 它们对应的整数或枚举类型的非类型模板参数具有相同的值
形式上,名称用于指代实体
basic.concept#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仍然是错误的。