简体   繁体   中英

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

I am learning C++17 new feature decltype(auto) for non-type template parameter. 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> .

However, The static_assert statement compiles with clang++, MSVC 19.27 but fails with 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

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

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

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

It's all in the rules describing how decltype works.

[dcl.type.simple]

4 For an expression e , the type denoted by decltype(e) is defined as follows:

  • 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;

  • 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 . If there is no such entity, or if e names a set of overloaded functions, the program is ill-formed;

  • otherwise, if e is an xvalue, decltype(e) is T&& , where T is the type of e ;

  • otherwise, if e is an lvalue, decltype(e) is T& , where T is the type of e ;

  • otherwise, decltype(e) is the type of e .

When using decltype(auto) , e is the expression that is used as an initializer for our object ( arg ). In the OP, this expression is x . It's an unparenthesized id-expression, so decltype(x) would be the type of the entity named by x . That type is int const , because a constexpr specifier implies const .

[dcl.constexpr]

9 A constexpr specifier used in an object declaration declares the object as 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.

So here's a cute modification to your code sample that makes GCC accept it.

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

Why is that? It's because +x is no longer an id-expression. It's a plain old prvalue expression of type int , having the same value as x . So that's what decltype(auto) deduces, an 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.

In C++17, I think GCC is wrong due to the following rules:
temp.type#1

Two template-ids refer to the same class, function, or variable if

1.1 their template-names , operator-function-ids, or literal-operator-ids refer to the same template and
[...]
1.3 their corresponding non-type template arguments of integral or enumeration type have identical values

Formally, a name is used to refer to the entity
basic.concept#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 {}; we declared. So the bullet 1.1 is first satisfied. Obviously, in this example, the corresponding template-arguments have the identical values, namely 42 . At least, according to what the c++17 standard says, they are the equivalence type. Hence GCC is wrong. In addition, GCC 7.5 agrees these types are equivalence.


However, something is changed in the latest draft. It introduces a new wording "template-argument-equivalent".
temp.type#1

Two template-ids are the same if

1.1 their template-names, operator-function-ids, or literal-operator-ids refer to the same template, and
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

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 . Instead, the deduced type for Foo<x> is 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.

Hence after conversion to the type of the template-parameter , these two values are of the same type, so they are template-argument-equivalent. So, talking this example under the c++20 standard, GCC is still wrong.

I think this is a gcc bug, and the static_assert should pass.

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:

...

(4.6) a type that contains a placeholder type.

The top-level cv-qualifiers on the template-parameter are ignored when determining its type.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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