TransWikia.com

How does the template argument deduction perform for function template parameter when it is a class template with default argument

Stack Overflow Asked on December 13, 2021

template<typename T, typename U = T>
struct Test{};

template<typename T>
void func(Test<T>){  //#1
}

int main(){
  func(Test<int>{});  //#2
}

Consider the above code, At the point of invocation of function template func, the type of argument is Test<int,int>, When call the function template, template argument deduction will perform.

The rule of template argument deduction for function call is :
temp.deduct#call-1

Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below.

I’m pretty sure the type of A is Test<int,int>, however I’m not sure what the type of P here is. Is it Test<T> or Test<T,T>, According to the rule, It seems to the type of P here is Test<T>, then deduction process is performed to determine the value of T that participate in template argument deduction. Then according to these rules described as the following:

temp.deduct#call-4

In general, the deduction process attempts to find template argument values that will make the deduced A identical to A (after the type A is transformed as described above).

temp.deduct#5

When all template arguments have been deduced or obtained from default template arguments, all uses of template parameters in the template parameter list of the template and the function type are replaced with the corresponding deduced or default argument values.

Because the class template Test has a default argument, hence the deduced T is substituted into default argument. That means the deduced A is Test<int,int> and it is identical to Argument type Test<int,int>.

However, It’s just my understanding. I’m not sure what type the P here is. If change the type of function argument to Test<int,double>, the outcome will report:

candidate template ignored: deduced conflicting types for parameter 'T' ('int' vs. 'double')

The outcome looks like as if the P is Test<T,T> and the fist value of T is conflicting with the second value of T.

So, My question is:

Whether the P here is Test<T> or Test<T,T>? and why?

4 Answers

Whether the P here is Test<T> or Test<T,T>? and why?

P is Test<T,T>.


I think we can agree that the rules of [temp.deduct] applies also for class templates; e.g. [temp.class.order], covering partial ordering of class template specializations, is entirely based on the concept of re-writing the class templates to (invented) function templates and applying the rules of function templates to that of the invented function templates corresponding to the original class templates under partial ordering analysis. Combined with the fact that the standard passage for class templates is quite brief in comparison to function templates, I interpret the references below as applying also for class templates.

Now, from [temp.deduct]/1 [emphasis mine]:

When a function template specialization is referenced, all of the template arguments shall have values. The values can be explicitly specified or, in some cases, be deduced from the use or obtained from default template-arguments. [...]

and, from [temp.deduct]/2 [emphasis mine]:

When an explicit template argument list is specified, the template arguments must be compatible with the template parameter list and must result in a valid function type as described below; otherwise type deduction fails. Specifically, the following steps are performed when evaluating an explicitly specified template argument list with respect to a given function template:

  • (2.1) The specified template arguments must match the template parameters in kind (i.e., type, non-type, template). There must not be more arguments than there are parameters unless [...]

With extra emphasis on "is referenced" and "the specified template arguments"; there is no requirement that we specify all arguments for a given matching function(/class) template, only that those that do specify follow the requirements of [temp.deduct]/2 for explicitly specified template arguments.

This leads us to back to [temp.deduct]/1 for the remaining template arguments of a given candidate function/class template: these can be either deduced (function templates) or obtained from the default template arguments. Thus, the call:

func(Test<int>{});

is, as per the argument above, semantically equivalent to

func(Test<int, int>{});

with the main difference that the template arguments for the former is decided by both an explicitly specified template arguments and a default template argument, whereas for the latter both are decided by explicitly specified template arguments. From this, it is clear that A is Test<int, int>, but we will use a similar argument for P.


From [temp.deduct.type]/3 [emphasis mine]:

A given type P can be composed from a number of other types, templates, and non-type values:

  • [...]
  • (3.3) A type that is a specialization of a class template (e.g., A<int>) includes the types, templates, and non-type values referenced by the template argument list of the specialization.

Notice that the description in [temp.deduct.type]/3.3 now returns to the template argument list of the template type P. It doesn't matter that P, for when inspecting this particular candidate function in overload resolution, refers to a class template by partly explicitly specifying the template argument list and partly relying on a default template parameter, where the latter is instantiation-dependent. This step of overload resolution does not imply any kind of instantiation, only inspection of candidates. Thus, the same rules as we just applied to the template argument A above applies to P, in this case, and as Test<int, int> is referenced (via Test<int>), P is Test<int, int>, and we have a perfect match for P and A (for the single parameter-argument pair P and A of this example)


Compiler error messages?

Based in the argument above, one could arguably expect a similar error message for the OP's failing example:

// (Ex1)
template<typename T, typename U = T>
struct Test{};

template<typename T>
void func(Test<T>) {}

int main() {
    func(Test<int, double>{});
}

as for the following simple one:

// (Ex2)
struct Foo {};
template<typename T> struct Test {};
template<typename T> void f(T) {}

int main() {
    f<Test<int>>(Test<Foo>{});
}

This is not the case, however, as the former yields the following error messages for GCC and Clang, respectively:

// (Ex1)

// GCC
error: no matching function for call to 'func(Test<int, double>)'
note:   template argument deduction/substitution failed:
        deduced conflicting types for parameter 'T' ('int' and 'double')

// Clang
error: no matching function for call to 'func'
note: candidate template ignored: deduced 
      conflicting types for parameter 'T' ('int' vs. 'double')

whereas the latter yields the following error messages for GCC and Clang, respectively:

// (Ex2)

// GCC
error: could not convert 'Test<Foo>{}' from 'Test<Foo>' to 'Test<int>'

// Clang
error: no matching function for call to 'f'
note: candidate function template not viable: 
      no known conversion from 'Test<Foo>' to 'Test<int>' for 1st argument

We can finally note that if we tweak (Ex1) into explicitly specifying the single template argument of f, both GCC and Clang yields similar error messages as for (Ex2), hinting that argument deduction has been entirely removed from the equation.

template<typename T, typename U = T>
struct Test{};

template<typename T>
void func(Test<T>) {}

int main() {
    func<int>(Test<int, double>{});
}

The key for this difference may be as specified in [temp.deduct]/6 [emphasis mine]:

At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

namely that the template argument deduction process is separated into a clear beginning and end, categorizing:

  • explicitly specified template arguments as the beginning of the process, and,
  • deduced or default argument-obtained template arguments as the end of the process,

which would explain the differences in the error messages of the examples above; if all template arguments have been explicitly specified in the beginning of the deduction process, the remainder of the process will not have any remaining template argument to work with w.r.t. deduction or default template arguments.

Answered by dfrib on December 13, 2021

P must be a type not a template. test <T> is a template-id, but it is not explicitly said in the standard that the template-id test <T> is equivalent to test<T,T>. The only thing that is said is:

A template-id is valid if

  • [...]
  • there is an argument for each non-deducible non-pack parameter that does not have a default template-argument, [...]

After that, holes in the standard are filled by our intuition oriented by the use of the term default.

I think the key point here is that a template designate a family, and a template-id cannot designate a family.

Answered by Oliv on December 13, 2021

not a language lawyer answer

There is no type Test<T> is actually a "shorthand" for Test<T, T>.

Just like with default function arguments if you have int foo(int a, int b = 24) the type of the function is int (int, int) and any call like foo(11) is actually foo(11, 24).

Answered by bolov on December 13, 2021

I tryed to come up with a code that forces only class deduction without function deduction.
Here, there are no function instantiations, but the compiler emits an error anyway:

template<typename T, typename U = T>
struct Test{};

template<typename T> 
void func(Test<T, T>){
}

template<typename T>
void func(Test<T>){  
}

redefinition of 'template<class T> void func(Test<T, T>)'

GCC: https://godbolt.org/z/7c981E
Clang: https://godbolt.org/z/G1eKTx

Previous wrong answer:

P refers to template parameter, not to template itself. In declaration Test<typename T, typename U = T> P refers to T, not to Test. So in the instantiation Test<int> T is int, just like A in the call is also int.

Answered by Cleiton Santoia Silva on December 13, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP