简体   繁体   English

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

[英]Using decltype(auto) in C++ non-type template parameter

I am learning C++17 new feature decltype(auto) for non-type template parameter.我正在为非类型模板参数学习 C++17 新特性decltype(auto) I wrote a simple code snippet as following:我写了一个简单的代码片段如下:

#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>>);
}

As I understand, Foo<42> should be the same type as Foo<x> .据我了解, Foo<42>应该与Foo<x>类型相同。

However, The static_assert statement compiles with clang++, MSVC 19.27 but fails with GCC 10.2, MSVC 19.25.但是, static_assert语句使用 clang++、MSVC 19.27 进行编译,但使用 GCC 10.2、MSVC 19.25 编译失败。

My question is: Why do compilers behave differently?我的问题是:为什么编译器的行为不同? What does the Standard say about this?标准对此有何评论?

Link to Compiler Explorer:链接到编译器资源管理器:

clang++ https://godbolt.org/z/66M695铛++ https://godbolt.org/z/66M695

gcc https://godbolt.org/z/3v5Mhd gcc https://godbolt.org/z/3v5Mhd

MSVC 19.25 https://godbolt.org/z/qP6v89 MSVC 19.25 https://godbolt.org/z/qP6v89

MSVC 19.27 https://godbolt.org/z/14aK5Y MSVC 19.27 https://godbolt.org/z/14aK5Y

It's all in the rules describing how decltype works.这一切都在描述decltype工作原理的规则中。

[dcl.type.simple] [dcl.type.simple]

4 For an expression e , the type denoted by decltype(e) is defined as follows: 4对于表达式e ,由decltype(e)表示的类型定义如下:

  • if e is an unparenthesized id-expression naming a structured binding ([dcl.struct.bind]), decltype(e) is the referenced type as given in the specification of the structured binding declaration;如果e是命名结构化绑定 ([dcl.struct.bind]) 的无括号 id 表达式,则decltype(e)是结构化绑定声明规范中给出的引用类型;

  • otherwise, if e is an unparenthesized id-expression or an unparenthesized class member access, decltype(e) is the type of the entity named by e .否则,如果e是无括号的 id 表达式或无括号的类成员访问,则decltype(e)是由e命名的实体的类型。 If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;如果没有这样的实体,或者如果 e 命名了一组重载函数,则程序格式错误;

  • otherwise, if e is an xvalue, decltype(e) is T&& , where T is the type of e ;否则,如果 e 是 xvalue,则decltype(e)T&& ,其中Te的类型;

  • otherwise, if e is an lvalue, decltype(e) is T& , where T is the type of e ;否则,如果 e 是左值,则decltype(e)T& ,其中Te的类型;

  • otherwise, decltype(e) is the type of e .否则, decltype(e)是的类型e

When using decltype(auto) , e is the expression that is used as an initializer for our object ( arg ).使用decltype(auto)e是用作对象 ( arg ) 初始值设定项的表达式。 In the OP, this expression is x .在 OP 中,这个表达式是x It's an unparenthesized id-expression, so decltype(x) would be the type of the entity named by x .这是一个不带括号的 id 表达式,因此decltype(x)将是由x命名的实体的类型。 That type is int const , because a constexpr specifier implies const .该类型是int const ,因为constexpr说明符暗示const

[dcl.constexpr] [dcl.constexpr]

9 A constexpr specifier used in an object declaration declares the object as const . 9在对象声明中使用的constexpr说明符将对象声明为const Such an object shall have literal type and shall be initialized.这样的对象应具有文字类型并应进行初始化。 In any constexpr variable declaration, the full-expression of the initialization shall be a constant expression.在任何constexpr变量声明中,初始化的完整表达式应为常量表达式。

So here's a cute modification to your code sample that makes GCC accept it.所以这是对您的代码示例的一个可爱的修改,使 GCC 接受它。

static_assert(std::is_same_v<Foo<42>, Foo<+x>>);

Why is that?这是为什么? It's because +x is no longer an id-expression.这是因为+x不再是 id 表达式。 It's a plain old prvalue expression of type int , having the same value as x .它是一个类型为int的普通纯右值表达式,与x具有相同的值。 So that's what decltype(auto) deduces, an int .所以这就是decltype(auto)推导出的int

All in all, the compilers that rejected your code are acting correctly.总而言之,拒绝您代码的编译器的行为是正确的。 And I guess it goes to show you that using decltype(auto) for a non-type template parameter should come with a short disclaimer.我想它会告诉你,对非类型模板参数使用decltype(auto)应该附带一个简短的免责声明。

In C++17, I think GCC is wrong due to the following rules:在 C++17 中,由于以下规则,我认为GCC是错误的:
temp.type#1温度类型#1

Two template-ids refer to the same class, function, or variable if两个模板 ID 引用同一个类、函数或变量,如果

1.1 their template-names , operator-function-ids, or literal-operator-ids refer to the same template and 1.1 它们的template-names 、 operator-function-ids 或literal-operator-ids 指的是同一个模板并且
[...] [...]
1.3 their corresponding non-type template arguments of integral or enumeration type have identical values 1.3 它们对应的整数或枚举类型的非类型模板参数具有相同的值

Formally, a name is used to refer to the entity正式地,名称用于指代实体
basic.concept#5基本概念#5

Every name that denotes an entity is introduced by a declaration.每个表示实体的名称都由声明引入。

So, whether Foo<42> or Foo<x> , their template-names all refer to the entity template<decltype(auto) arg> struct Foo {};因此,无论是Foo<42>还是Foo<x> ,它们的模板名称都指向实体template<decltype(auto) arg> struct Foo {}; we declared.我们宣布。 So the bullet 1.1 is first satisfied.所以首先满足子弹1.1 Obviously, in this example, the corresponding template-arguments have the identical values, namely 42 .显然,在这个例子中,对应的模板参数具有相同的值,即42 At least, according to what the c++17 standard says, they are the equivalence type.至少,根据 c++17 标准的说法,它们是等价类型。 Hence GCC is wrong.因此GCC是错误的。 In addition, GCC 7.5 agrees these types are equivalence.此外, GCC 7.5同意这些类型是等价的。


However, something is changed in the latest draft.然而,在最新的草案中有所改变。 It introduces a new wording "template-argument-equivalent".它引入了一个新的措辞“模板参数等效”。
temp.type#1温度类型#1

Two template-ids are the same if两个模板 ID 相同,如果

1.1 their template-names, operator-function-ids, or literal-operator-ids refer to the same template, and 1.1 它们的模板名称、操作符函数 ID 或文字操作符 ID 指的是同一个模板,并且
1.2 ... 1.2 ...
1.3 their corresponding non-type template-arguments are template-argument-equivalent (see below) after conversion to the type of the template-parameter 1.3 它们对应的非类型模板参数在转换为模板参数的类型后是模板参数等价的(见下文)

And
template-argument-equivalent模板参数等效

Two values are template-argument-equivalent if they are of the same type and两个值是模板参数等价的,如果它们是相同的类型并且

they are of integral type and their values are the same它们是整数类型并且它们的值相同

As said in other answers, the deduced type for Foo<42> is int .正如其他答案中所说, Foo<42>的推导类型是int Instead, the deduced type for Foo<x> is int const .相反, Foo<x>的推导类型是int const Although their deduced types are different, However, such a rule should be obeyed:虽然它们的推导类型不同,但是,应该遵守这样的规则:

The top-level cv-qualifiers on the template-parameter are ignored when determining its type.确定模板参数的类型时,将忽略模板参数上的顶级 cv 限定符。

Hence after conversion to the type of the template-parameter , these two values are of the same type, so they are template-argument-equivalent.因此after conversion to the type of the template-parameter ,这两个值是相同的类型,因此它们是模板参数等价的。 So, talking this example under the c++20 standard, GCC is still wrong.所以,在c++20标准下讲这个例子, GCC仍然是错误的。

I think this is a gcc bug, and the static_assert should pass.我认为这是一个 gcc 错误,并且static_assert应该通过。

According to this :根据这个

If the type of a template-parameter contains a placeholder type, the deduced parameter type is determined from the type of the template-argument by placeholder type deduction.如果模板参数的类型包含占位符类型,则通过占位符类型推导从模板参数的类型确定推导的参数类型。 ... ...

Placeholder type deduction in this context mean the type of the parameter is deduced as if deduced by these invented declarations:在此上下文中的占位符类型推导意味着推导参数的类型,就像通过这些发明的声明推导一样:

decltype(auto) t = 42; // for Foo<42> : int
decltype(auto) t = x;  // for Foo<x> : int const

And then, according to this :然后,根据这个

A non-type template-parameter shall have one of the following (optionally cv-qualified) types:非类型模板参数应具有以下类型之一(可选 cv 限定):

... ...

(4.6) a type that contains a placeholder type. (4.6) 包含占位符类型的类型。

The top-level cv-qualifiers on the template-parameter are ignored when determining its type.确定模板参数的类型时,将忽略模板参数上的顶级 cv 限定符。

Since the top-level qualifiers are ignored when determining the type according to the invented declarations, both Foo<42> and Foo<x> should have the same type, and the static_assert should pass.由于根据发明的声明确定类型时会忽略顶级限定符,因此Foo<42>Foo<x>应该具有相同的类型,并且static_assert应该通过。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM