我正在学习非类型模板参数的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进行编译。
我的问题是:为什么编译器的行为不同?标准对此有何看法?
链接到编译器资源管理器:
clang ++ 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
答案 0 :(得分:14)
这是描述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
的普通旧prvalue表达式,具有与x
相同的值。这就是decltype(auto)
得出的int
。
总而言之,拒绝您的代码的编译器运行正常。而且我想它向您展示了,将decltype(auto)
用于非类型模板参数应该带有简短的免责声明。
答案 1 :(得分:5)
在C ++ 17中,由于以下规则,我认为GCC
是错误的:
temp.type#1
两个模板ID引用相同的类,函数或变量,如果
1.1其模板名称,操作员功能ID或文字操作员ID指的是同一模板和
[...]
1.3它们对应的整数或枚举类型的非类型模板参数具有相同的值
在形式上,名称用于引用实体
basic.concept#5
每个表示实体的名称都由声明引入。
因此,无论是Foo<42>
还是Foo<x>
,它们的模板名称都引用了我们声明的实体template<decltype(auto) arg> struct Foo {};
。因此,首先满足项目符号1.1
的要求。显然,在此示例中,相应的模板参数具有相同的值,即42
。至少,按照c ++ 17标准所说的,它们是等效类型。因此GCC
是错误的。另外,GCC 7.5同意这些类型是等效的。
但是,最新草案中有一些更改。它引入了新的措辞“等价的模板参数”。
temp.type#1
如果两个模板ID相同,则
1.1它们的模板名称,操作员功能ID或文字操作员ID指的是同一模板,并且
1.2 ...
1.3它们对应的非类型模板参数与模板参数等效(请参见下文)转换为模板参数的类型
和
template-argument-equivalent
如果两个值是相同类型和
,则它们是模板参数等效的它们是整数类型,并且它们的值相同
如其他答案所述,Foo<42>
的推导类型为int
,而Foo<x>
的推导类型为int const
。它们是不同的类型,因此after conversion to the type of the template-parameter
这两个值不是同一类型,因此即使它们的值相同,它们也不是模板参数等效的。因此,在c ++ 20标准下讨论此示例,GCC
是正确的。
答案 2 :(得分:4)
我认为这是一个gcc错误,static_assert
应该会通过。
根据this:
在这种情况下,如果模板参数的类型包含占位符类型,则通过占位符类型推导从模板参数的类型中确定推导的参数类型。 ...
占位符类型推论意味着推论该参数的类型,就像这些发明的声明推论的那样:
decltype(auto) t = 42; // for Foo<42> : int
decltype(auto) t = x; // for Foo<x> : int const
然后,根据this:
非类型模板参数应具有以下(可选,具有cv限定)类型之一:
...
(4.6)包含占位符类型的类型。
确定模板参数的类型时,会忽略模板参数上的顶级简历限定符。
由于根据发明的声明确定类型时会忽略顶级限定符,因此Foo<42>
和Foo<x>
应该具有相同的类型,并且static_assert
应该通过。 / p>