13
#include<type_traits>

template <typename T, T>
struct A { };

template <typename T, T t>
void f(A<T, t>) {
}

int main() {
    f(A<const int, 0>{});
}

https://godbolt.org/z/n6bcj5rjM


The program is accepted by GCC and ICC in C++14 and C++17 mode, as well as Clang in C++14 mode but rejected by Clang in C++17 mode and MSVC in either mode.


Rejections are with diagnostics like this:

<source>:12:5: error: no matching function for call to 'f'
    f(A<const int, 0>{});
    ^
<source>:7:6: note: candidate template ignored: deduced conflicting types for parameter 'T' ('const int' vs. 'int')
void f(A<T, t>) {
     ^

Also consider the following variation:

const int i = 0;

int main() {
    f(A<const int&, i>{});
}

https://godbolt.org/z/oa3xfv4jx

The same compilers still accept/reject and those rejecting now complain about a type mismatch between const int& and int.


Which compiler(s) are correct?


I expect the answer to depend on whether it is before or after C++17, since C++17 introduced a (breaking) change in non-type template argument deduction, see [diff.cpp14.temp].

In particular for C++17 and later I am wondering whether deduction of T from the argument for t is supposed to deduce the type of the template parameter, which would be int in the first variant (because top-level const is ignored) and const int& in the second variant, or whether the usual expression adjustments are supposed to be applied as in deduction with auto ... = t;, in which case the second variant should deduce T to int.

Is it then really intended that template argument deduction fails if these types mismatch with the explicitly provided type for T?

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • 2
    2 observations - (1) the type of `0` is `int` not `const int` and (2) if you make the change `f(A{});` then the code is accepted by all compilers. – Richard Critten Feb 12 '22 at 00:24
  • _I would like to understand what the rules here are for the deduction of `T`_ https://stackoverflow.com/questions/55033649/type-of-non-type-parameter-in-template-template-class-is-non-deducible-in-c14? – Language Lawyer Feb 12 '22 at 00:44
  • @LanguageLawyer Regarding the duplicate: I am not concerned with whether or not the type can be deduced from the non-type argument, but whether 1. the non-type template argument should be used to deduce `T` at all, since `T` is already specified by the first template argument and 2. whether the deduction should yield `int`, the type of the value `0`, or `const int`, the type of the non-type template parameter of the specialization. In the former case deduction should fail, I guess, but in the latter it shouldn't. – user17732522 Feb 12 '22 at 00:58
  • _the non-type template argument should be used to deduce `T` at all, since `T` is already specified by the first template argument_ https://timsong-cpp.github.io/cppwp/n4868/temp.deduct.type#9.sentence-1 (https://timsong-cpp.github.io/cppwp/n4868/temp.deduct.type#8.sentence-2) – Language Lawyer Feb 12 '22 at 01:05
  • @LanguageLawyer I have removed the `static_assert` part, since that was indeed stupid. Your other links seem clear, but that still leaves the question of whether or not deducing `T` to `int` from the non-type template argument should really cause deduction failure, so that Clang is the only correct one in both modes. That at least seems unintended to me. – user17732522 Feb 12 '22 at 01:13
  • @user17732522 _I have removed the static_assert part, since that was indeed stupid_ But the type of NTTP is `const int` and `decltype` shall produce it. – Language Lawyer Feb 12 '22 at 01:18
  • 2
    @LanguageLawyer https://timsong-cpp.github.io/cppwp/n4868/temp.param#6 seems to say that `const` is ignored. – user17732522 Feb 12 '22 at 01:20
  • Another interesting variation `template t> void f(A) {}` is accepted by all while `template & t> void f(A) {}` is accepted by gcc and msvc in both modes and rejected by clang and icc in both modes. – Ted Lyngmo Feb 27 '22 at 11:55
  • @TedLyngmo I assume you are referring to the first code example. `std::remove_reference_t` results in `T` not being deduced. That is also how it should be before C++17 in the original. In that case, I think there should indeed not be a conflict (the types are correct before deduction). I am not sure what the intention with `std::remove_reference_t&` is, but that seems to effectively result in what [this question](https://stackoverflow.com/questions/71283266/auto-deduction-of-reference-template-argument-from-not-reference-type-in-c?noredirect=1&lq=1) asks about. – user17732522 Feb 27 '22 at 11:58
  • @user17732522 Yes, I was thinking about the first example. My second variant was to try to force the non-type parameter into a `T&` but msvc won't accept `template void f(A) {}` (`const` vs. non-`const` ambiguity). Removing the ref first makes msvc also accept it. I actually came to this question via the question you referred to :-) – Ted Lyngmo Feb 27 '22 at 12:08

1 Answers1

1

The explanation given below is for the 1st snippet(also shown below):

//----------------------v--->i named the parameter for explanation purposes
template <typename T, T p>
struct A { };

template <typename T, T t>
void f(A<T, t>) {
}

int main() {
    f(A<const int, 0>{});
}

Step 1

Here we consider what happens for the expression A<const int, 0>{}.

From temp.param#5:

The top-level cv-qualifiers on the template-parameter are ignored when determining its type.

On the first glance it seems that when applied to the given snippet, the non-type template parameter named p should be int instead of const int.

But note the emphasis on the highlighted part in the above quoted statement. In particular, my interpretation of this(the phrase "when determining") is that the top-level cv-qualifiers are dropped when deducing the template parameter and so not when the template arguments are explicitly specified.

And since in my given snippet, we are explicitly specifying the template argument, there is no template argument deduction(TAD) here. Thus, the dropping of the top-level cv-qualifiers due to the above quoted temp.param#5 does not happen. Meaning the type of p is const int instead of int.

Note that i may be wrong in interpreting [temp.param#5] above.


But before further analysis, from temp.param#6:

A non-type non-reference template-parameter is a prvalue. It shall not be assigned to or in any other way have its value changed.

Moreover, from expr#6:

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

When applying temp.param#6 and expr#6 to the given snippet, it means that p(a prvalue) will finally be of type int instead of const int.

Thus, the non-type template parameter p is of type int.

Step 2

Next, we consider what happens for the call expression f(A<const int, 0>{}).

Now currently we have a prvalue A<const int, 0> whose first type parameter T is const int while the second non-type template parameter p is of type int. Thus, for the function template f<> the first template type parameter T is deduced to be const int while the type of non-type template parameter t is deduced to be int. Thus there is a conflict between the type of T and hence the mentioned error saying:

<source>:7:6: note: candidate template ignored: deduced conflicting types for parameter 'T' ('const int' vs. 'int')
void f(A<T, t>) {
     ^
Jason
  • 36,170
  • 5
  • 26
  • 60