24

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

4
  • Why not just auto? (There are many valid reasons, but it is possible you don't even know it would work) Commented Oct 18, 2020 at 17:19
  • 3
    @Yakk-AdamNevraumont: I am learning the new feature so I want to try things to double check my understanding. Commented Oct 18, 2020 at 17:26
  • <godbolt.org/z/cWvKaj>: gcc seems to think Foo<x>'s template argument is of type const int (So Foo<(const int) 42>), whereas Foo<42> is just int Commented Oct 18, 2020 at 17:28
  • @Artyer: Thanks. Now I think gcc may be correct because type of x is const int. However, C++ Standard is full of weird cases so I can not be so sure. :) Commented Oct 18, 2020 at 17:51

3 Answers 3

15

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.

Sign up to request clarification or add additional context in comments.

17 Comments

I'm agree with you but there is a point that isn't clear to me: where, in the C++17 standard, is stated that Foo<x> and Foo<y> are different types when x and y are the same value of different types (as Foo<42> and Foo<42L>)? In "Type Equivalence" (14.4) I read (1.4) "their corresponding non-type template arguments of integral or enumeration type have identical values". But "identical values" require also identical types? Also regarding the constness?
@max66 - "identical values" doesn't require const qualifiers. It's a property of types, not values. But on another note, the CWG issue list is full of template related shenanigans, so I can't really speak with too much authority on anything but the most obvious cases.
@cigen - Sure. Buth which rule say that Foo<(int)49> is different from Foo<(char)49>? I mean: which rule say that two templates, with a template non-type parameter, are different when the non-type parameters are equals in value but different in types?
@mibu - Well, if you are hosting a social event for C++ developers and need a sort of "pub quiz" topic. I honestly haven't seen a situation where you'd decltype deduction semantics for templates.
@cigien According to A class template shall not have the same name as any other template, class, function, variable, enumeration, enumerator, namespace, or type in the same scope, that means it cannot exist two different class template with the same name in the same scope. how could exist template<int> struct Foo and template<int const> struct Foo in the same scope (deduced by template-arguments)?
|
5

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.

6 Comments

Yes, I think this all looks correct. It should be a defect report I feel, the c++20 behavior seems correct :)
@cigien After making some efforts, I found this issue that made the wording be modified.
@cigien / jack X: I just found this interesting Q&A (I missed it in October). This answer seems reasonable, but so does StoryTeller's answer (as well as your own cigien). Is this simply underspecified in the standard (such that both answers are "correct" interpretations, in a sense), or is one answer actually more correct than the other ones? I've read through all three several times and I can't decide myself :)
@dfrib I gave a link in this answers that indicates the different behaviors between GCC7.5 and GCC(higher version), they gave the different result. That's sufficient to evidence that GCC follows the corresponding rule that modified in different draft.
@dfrib Yeah, it's a tricky one. Not sure if it's underspecified, or just poorly worded though :p I do like jack X's argument re the change in gcc's behavior, it does seem to be in response to wording changes. But I wouldn't count that as conclusive at all.
|
4

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.

16 Comments

Mmm... it's the cv-qualifiers on the parameter, not the argument. So if I parse it correctly, something like const auto is described here... but then again, the const doesn't change things normally, so why bother mentioning it. Aiee
Also, you cite the latest draft, not C++17. The closer to C++17 draft is here timsong-cpp.github.io/cppwp/n4659/temp.param#4
@StoryTeller-UnslanderMonica Yes, the expressions passed as arguments to the template do have qualifiers, but for deducing the type of the parameter I think those qualifiers are dropped.
@cigien - Yes, decltype(auto) must be the only type specifier, so it doesn't even stumble upon this. For what it's worth, GCC agrees with my interpretation as far as const auto goes godbolt.org/z/r4G5jo.
I'm not sure the people on the CWG mailing list will agree it's a lovely day if this issue is raised :P
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.