Reputation: 1557
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
Upvotes: 24
Views: 2646
Reputation: 7369
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.
Upvotes: 5
Reputation: 60440
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.
Upvotes: 4
Reputation: 170279
It's all in the rules describing how decltype
works.
[dcl.type.simple]
4 For an expression
e
, the type denoted bydecltype(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 bye
. 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)
isT&&
, whereT
is the type ofe
;otherwise, if e is an lvalue,
decltype(e)
isT&
, whereT
is the type ofe
;otherwise,
decltype(e)
is the type ofe
.
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 asconst
. Such an object shall have literal type and shall be initialized. In anyconstexpr
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.
Upvotes: 15