23

我正在学习decltype(auto)非类型模板参数的 C++17 新特性。我写了一个简单的代码片段如下:

#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

4

3 回答 3

15

这一切都在描述如何decltype工作的规则中。

[dcl.type.simple]

4对于表达式e,由 表示的类型decltype(e)定义如下:

  • ife是命名结构化绑定 ([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 表达式。这是一个普通的旧纯右值表达式 type int,具有与 相同的值x。所以这就是decltype(auto)推论,一个int.

总而言之,拒绝您的代码的编译器运行正常。我想它会告诉你,使用decltype(auto)非类型模板参数应该附带一个简短的免责声明。

于 2020-10-18T17:53:46.013 回答
5

在 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同意这些类型是等价的。


然而,在最新的草案中有所改变。它引入了一个新的措辞“模板-参数-等效”。
temp.type#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仍然是错误的。

于 2020-10-19T13:26:44.580 回答
4

我认为这是一个 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应该通过。

于 2020-10-18T18:05:21.593 回答