在C ++非类型模板参数中使用decltype(auto)

时间:2020-10-18 16:49:04

标签: c++ c++17 language-lawyer decltype-auto

我正在学习非类型模板参数的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

3 个答案:

答案 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&&,其中Te的类型;

  • 否则,如果e是左值,则decltype(e)T&,其中Te的类型;

  • 否则,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>

相关问题