Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 485
mibu Avatar asked Oct 18 '20 16:10

mibu


People also ask

What is the difference between auto and decltype?

auto is a keyword in C++11 and later that is used for automatic type deduction. The decltype type specifier yields the type of a specified expression. Unlike auto that deduces types based on values being assigned to the variable, decltype deduces the type from an expression passed to it.

What is decltype used for?

In the C++ programming language, decltype is a keyword used to query the type of an expression. Introduced in C++11, its primary intended use is in generic programming, where it is often difficult, or even impossible, to express types that depend on template parameters.

What does decltype return?

decltype returnsIf what we pass to decltype is the name of a variable (e.g. decltype(x) above) or function or denotes a member of an object ( decltype x.i ), then the result is the type of whatever this refers to. As the example of decltype(y) above shows, this includes reference, const and volatile specifiers.

What does decltype stand for?

Decltype keyword in C++ Decltype stands for declared type of an entity or the type of an expression. It lets you extract the type from the variable so decltype is sort of an operator that evaluates the type of passed expression. SYNTAX : decltype( expression )

How to write a non-type parameter in a C++ template?

Prior to C++17, when writing a template non-type parameter, you had to specify its type first. So a common pattern became writing something like: template <class T, T N> struct integral_constant { using type = T; static constexpr T value = N; }; using five = integral_constant<int, 5>;

What is decltype(auto) with no trailing return type?

In C++14, you can use decltype(auto) with no trailing return type to declare a template function whose return type depends on the types of its template arguments.

What is decltype in C++?

decltype (C++) 1 Return Value. The type of the expression parameter. 2 Remarks. The decltype type specifier is supported in Visual Studio 2010 or later versions, and can be used with native or managed code. ... 3 Decltype and Auto. ... 4 Examples. ... 5 Requirements. ...

Can the return type of the template function depend on the arguments?

For example, consider the following code example in which the return type of the template function depends on the types of the template arguments. In the code example, the UNKNOWN placeholder indicates that the return type cannot be specified.


Video Answer


3 Answers

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.

like image 102
StoryTeller - Unslander Monica Avatar answered Oct 14 '22 07:10

StoryTeller - Unslander Monica


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.

like image 27
xmh0511 Avatar answered Oct 14 '22 06:10

xmh0511


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.

like image 35
cigien Avatar answered Oct 14 '22 08:10

cigien