Search code examples
c++templatesgccclanglanguage-lawyer

Compiler variance for the type of deduced (non-type) template parameter


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

GCC demo, Clang demo.

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).

GCC demo, Clang demo.

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.


Solution

  • 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 by decltype(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 type const 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: