Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ template functions priority

Tags:

#include <iostream>  template <class U, class T> void foo(U&, T&) {     std::cout << "first"; }  template <class T> void foo(int&, const T&) {     std::cout << "second"; }  int main() {     int a;     double g = 2.;     foo(a, g); // prints "first"      return 0; } 

To call the second foo overload, the compiler needs to perform only one template type deduction, but for the first overload, it needs to perform two. Can you please explain why the first overload is called?

like image 421
Ashot Avatar asked Jun 25 '15 10:06

Ashot


People also ask

Are template functions always inline?

The template function must be inline and visible, typically this is all done in the header file. You wouldn't normally place the code in a . cpp file to be compiled as a separate module.

What is function template C?

Templates Specialization is defined as a mechanism that allows any programmer to use types as parameters for a class or a function. A function/class defined using the template is called a generic function/class, and the ability to use and create generic functions/classes is one of the critical features of C++.

What is the need of template functions in C?

Templates are powerful features of C++ which allows us to write generic programs. We can create a single function to work with different data types by using a template.

What is the correct syntax of defining function template template functions?

What is the correct syntax of defining function template/template functions? Explanation: Starts with keyword template and then <class VAR>, then use VAR as type anywhere in the function below. 7.


2 Answers

Overload resolution is done in multiple steps.

First, through name lookup, we select the list of viable candidates. In this case, that is:

template <class U, class T> void foo(U&, T&);            // with U = int, T = double  template <class T> void foo(int&, const T&)     // with T = double 

Next, we determine the conversion sequence necessary for each argument for each viable candidate. This is [over.ics.rank]:

Standard conversion sequence S1 is a better conversion sequence than standard conversion sequence S2 if [...]

  • S1 is a proper subsequence of S2 (comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that,
  • the rank of S1 is better than the rank of S2, or S1 and S2 have the same rank and are distinguishable by the rules in the paragraph below, or, if not that,

For the first call, the conversion sequence is (Identity, Identity). For the second call, the conversion sequence is (Identity, Identity). So we're equal there. Neither of those bullet points distinguish the two calls. So we move on.

  • S1 and S2 are reference bindings (8.5.3) and neither refers to an implicit object parameter of a non-static member function declared without a ref-qualifier, and S1 binds an rvalue reference to an rvalue and S2 binds an lvalue reference.

Irrelevant.

  • S1 and S2 are reference bindings (8.5.3) and S1 binds an lvalue reference to a function lvalue and S2 binds an rvalue reference to a function lvalue.

Nope.

  • S1 and S2 differ only in their qualification conversion and yield similar types T1 and T2 (4.4), respectively, and the cv-qualification signature of type T1 is a proper subset of the cv-qualification signature of type T2.

Qualification conversion is a pointer thing, nope.

  • S1 and S2 are reference bindings (8.5.3), and the types to which the references refer are the same type except for top-level cv-qualifiers, and the type to which the reference initialized by S2 refers is more cv-qualified than the type to which the reference initialized by S1 refers.

In this case, the first overload takes its second argument as double& while the second overload takes a const double&. The former is less cv-qualified than the latter, so we stop here - preferring foo(U&,T&).

Only after the steps to determine which conversion sequence is better do we get to the step that the more specialized template is preferred. The full rule ordering in [over.match.best] is:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if for all arguments i, ICSi(F1) is not a worse conversion sequence than ICSi(F2), and then

  • for some argument j, ICSj(F1) is a better conversion sequence than ICSj(F2), or, if not that,

That's what we just went through.

  • the context is an initialization by user-defined conversion [ ... ]
  • the context is an initialization by conversion function for direct reference binding [ ... ]
  • F1 is not a function template specialization and F2 is a function template specialization, or, if not that,
  • F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 14.5.6.2.

That's why we pick foo(U&, T&). If you remove the const, however, then both conversion sequences are identical across all steps - so at that point, the more specialized template (foo(int&, T&)) would win.

Note that more specialized is the very last mechanism to determine best candidate. It's the most final of tie breakers.

Also note that the number of template deductions is irrelevant. It may matter in selecting between overload that is a template and an overload that is not a template - but it does not matter in selecting between an overload that has x template parameters and an overload that has y > x template parameters.

like image 139
Barry Avatar answered Oct 27 '22 01:10

Barry


You declare in the second function that you second argument to be const. The below example of your with applied correction calls the second one:

#include <iostream>  template <class U, class T> void foo(U&, T&) {     std::cout << "first"; }  template <class T> void foo(int&, T&) {     std::cout << "second"; }  int main() {     int a;     double g = 2.;     foo(a, g);      return 0; } 

On the other hand, when you explicitly declare second argument do be const in main(), the application calls second function in your above example as expected:

#include <iostream>  template <class U, class T> void foo(U&, T&) {     std::cout << "first"; }  template <class T> void foo(int&, const T&) {     std::cout << "second"; }  int main() {     int a;     const double g = 2.;     foo(a, g);      return 0; } 
like image 42
PiotrSliwa Avatar answered Oct 26 '22 23:10

PiotrSliwa