4

Here is the code I'm not understanding

#include<iostream>

using namespace std;

template <typename T> 
T calc(T, T) { cout << "template calc" << endl; }

double calc(double, double) { cout << "ordinary calc" << endl; }

template <> 
char calc<char>(char, char) { cout << "template specialisation calc" << endl; }

int main() {

   int ival;
   double dval;
   float fd;

   calc(0, ival); // calls the generic calc(T, T)

   // the following all call calc(double, double)
   calc(0.25, dval);
   calc(0, fd);
   calc(0, 'J');

   calc('I', 'J'); // calls calc(char, char)
}

There are 5 function calls to calc, so I will refer to them as as 1) - 5) depending on their position.

1) kind of makes sense. 0 is an integer, ival is an integer, it makes sense that calc(T, T) is called. Altho I feel like my reasoning on this is wrong. After all, they are both doubles, so if calc(double, double) were called, that would make sense too. So looking for clarification here.

2) no dramas, both are doubles, call calc(double, double). Simple.

3) fd is a float. Can either call calc(T, T) or calc(double, double). Because 1) resulted in a call to calc(T, T) I would assume that the same would hold here since we again have one param being 0, yet, this calls calc(double, double). So this confuses me, particularly the difference between this and 1)

4) My first thought was 0 is a valid character, so is 'J', so it calls calc(char, char). It might call calc(T, T) using a common type of integer. But nope, both wrong, it calls calc(double, double). Im very confused about this one. Makes no sense to me.

5) makes sense, but only if I apply the same logic as I did for 4), which was wrong in 4), so if I were to see another example of using templates I would not be sure as to what logic to apply.

So looking for explanation of why this program does what it does.

Thanks.

  • In static binding, the compiler prefers ordinary overloading rules to template specializations: it resorts to the latter only when it cannot figure out an overload which is OK with the parameters at hand. I cannot quote the relevant bits of the standard, though. – akappa Nov 03 '12 at 08:25
  • If a function template arguments do not match *exactly* (i.e. an implicit type conversion is needed), the template is not considered as a candidate. – n. m. could be an AI Nov 03 '12 at 08:26
  • @n.m.: but even in that case the compiler prefers an overload, even if it needs some conversions in order to call it, right? – akappa Nov 03 '12 at 08:27
  • The question you should also be asking is how many of these will compile if calc(double,double) is NOT in the picture at all, and by that I mean specifically targeting the fourth. try it. – WhozCraig Nov 03 '12 at 08:48

2 Answers2

1

First of all, when you write literal "0" in the source code, it's of type "int". And when looking for which function to call, the compiler choose the best matching one. If not available, the compiler will do some implicit type conversion to make it work.

1)Both 0 and ival are int

2)For double and double, calc(double, double) matches exactly

3)For int and float, no matching found, so both converted to double

4)For int and char, same as 3)

5)For char and char, calc(char, char) matches exactly

luhb
  • 656
  • 5
  • 4
1

I suggest you read up on overload resolution:

The process of selecting the most appropriate overloaded function or operator is called overload resolution.

Suppose that f is an overloaded function name. When you call the overloaded function f(), the compiler creates a set of candidate functions. This set of functions includes all of the functions named f that can be accessed from the point where you called f(). The compiler may include as a candidate function an alternative representation of one of those accessible functions named f to facilitate overload resolution.

After creating a set of candidate functions, the compiler creates a set of viable functions. This set of functions is a subset of the candidate functions. The number of parameters of each viable function agrees with the number of arguments you used to call f().

The compiler chooses the best viable function, the function declaration that the C++ run-time environment will use when you call f(), from the set of viable functions. The compiler does this by implicit conversion sequences. An implicit conversion sequence is the sequence of conversions required to convert an argument in a function call to the type of the corresponding parameter in a function declaration. The implicit conversion sequences are ranked; some implicit conversion sequences are better than others. The best viable function is the one whose parameters all have either better or equal-ranked implicit conversion sequences than all of the other viable functions. The compiler will not allow a program in which the compiler was able to find more than one best viable function.

1, 2, and 5 are all exact matches and so no conversions are necessary.

To explain 3 and 4, note that your program will not compile if you remove the double, double function. This is because the template argument cannot be deduced for 3 and 4 (because the argument types don't match). Because of this, only the double, double function is considered a candidate function.

Pubby
  • 51,882
  • 13
  • 139
  • 180