Reputation: 73206
Consider the following example:
#include <type_traits>
struct A {};
template <A const i> void f() {
static_assert(std::is_same_v<decltype(i), A const>); // #1
A& ar = i; // #2
}
int main() {
f<A{}>();
}
which both Clang(1) and GCC(1) rejects with the following two seemingly conflicting errors:
#1 error: static_assert failed due to requirement 'std::is_same_v<A, const A>' #2 error: binding reference of type 'A' to value of type 'const A' drops 'const' qualifier
Moreover, if we change the type of the non-type template parameter into a placeholder type as follows:
template <auto const i> void g() {
static_assert(std::is_same_v<decltype(i), A const>); // #1
A& ar = i; // #2
}
Then GCC accepts #1
, whereas Clang rejects it (both reject #2
as above).
What is going on here, and which compiler is correct (if any)?
(1) GCC HEAD 11.0.0 20210117 and Clang HEAD 12.0.0 (20210118), -std=c++20
.
Upvotes: 2
Views: 126
Reputation: 73206
Both GCC and Clang are (probably) correct to emit errors for both #1
and #2
for f
.
As per [temp.param]/6 [emphasis mine]:
A non-type template-parameter shall have one of the following (possibly cv-qualified) types:
- (6.1) a structural type (see below),
- (6.2) a type that contains a placeholder type ([dcl.spec.auto]), or
- (6.3) a placeholder for a deduced class type ([dcl.type.class.deduct]).
The top-level cv-qualifiers on the template-parameter are ignored when determining its type.
the top-level cv-qualifiers are ignored when determining its type; it's arguably somewhat vague what "determining" refers to for (6.1), as compared to (6.2) and (6.3) which deals with placeholder types that need to be deduced.
[dcl.type.decltype]/1 [emphasis mine]:
For an expression
E
, the type denoted bydecltype(E)
is defined as follows:
- [...]
- otherwise, if
E
is an unparenthesized id-expression naming a non-type template-parameter,decltype(E)
is the type of the template-parameter after performing any necessary type deduction ([dcl.spec.auto], [dcl.type.class.deduct]);- [...]
mentions that the type of decltype(i)
in f
is the type of the non-type template parameter, after performing any necessary type deduction, followed by two references that connect to [temp.param]/6.2 and [temp.param]/6.3.
The key for #1
in f
is thus whether or not [temp.param]/6.1 also undergoes "determining of its type", even when its clearly been annotated as A const
(no deduction needed). Both GCC and Clang seems to agree that this is the case: A const
undergoes "determining of type" and const
is removed.
Understanding #2
in f
is more straightforward; the actual template parameter object associated with the non-type template parameter (of class type) A
is not necessarily the same type as non-type template parameter; instead its type is governed by [temp.param]/8 [emphasis mine]:
An id-expression naming a non-type template-parameter of class type
T
denotes a static storage duration object of typeconst T
, known as a template parameter object, whose value is that of the corresponding template argument after it has been converted to the type of the template-parameter. All such template parameters in the program of the same type with the same value denote the same template parameter object. A template parameter object shall have constant destruction ([expr.const]). [ Note: If an id-expression names a non-type non-reference template-parameter, then it is a prvalue if it has non-class type. Otherwise, if it is of class type T, it is an lvalue and has type const T ([expr.prim.id.unqual]). — end note ]
such that that the template parameter object for any class type T
always have the type const T
, thus explaining the error at #2
. Note that the latter only holds for class types, as an id-expression naming a non-type non-reference template_parameter is a prvalue.
As for the discrepancy between GCC and Clang at #1
in g
, there is no vagueness when applying [temp.param]/6 , particularly [temp.param]/6.2, applying type deduction whilst ignoring the cv-qualifiers on the template-parameter. Meaning decltype(i)
is A
in g
, and GCC is wrong to accept line #1
. I have filed the following GCC bug report accordingly:
Upvotes: 2