3

I was playing around with function templates and I stumbled across a weird interaction.

template<class T1, class T2>
void foo(T1, T1);

template<class T1, class T2>
void foo(T1, T2);

//main
foo(1,1)

This calls foo(T1, T2) and I don't understand why. How would that work? Are these functions overloads of each other, and why would the compiler choose the one with different parameter types?

this particular interaction was explained in the first part of Henri Menke's post


After some more messing around I found something weirder

#include <iostream>

template<class T1, class T2>
void foo(T1 a, T1 b)
{
    std::cout << "same\n";
}

template<class T1, class T2>
void foo(T1 a, T2 b)
{
    std::cout << "different\n";
}

int main()
{
    foo(1, 1);
    foo<int, int>(1, 1);
}

In this code I get a result

different
different

but after commenting out the 1st call like

int main()
{
    //foo(1, 1);
    foo<int, int>(1, 1);
}

the result is

same

I'm using VS2015 and if I write the same thing in Ideone (like here) the result for the 1st one is

different
same

Could somebody be able to explain what is (or isn't) going on?


By the way I came to the conclusion that the call foo<int, int>(1, 1); should be ambiguous. Both function templates have the same signature then and come from the same template. So that is another thing, why don't they collide?

Jakub Dąbek
  • 1,044
  • 1
  • 8
  • 17
  • 3
    How could it *possibly* use the first function template? What would `T2` be? – Kerrek SB Apr 30 '17 at 22:31
  • It calls foo(int, int) but I want to know how it chooses, which template parameters become function parameter types – Jakub Dąbek Apr 30 '17 at 22:40
  • 2
    Maybe you don't have the right grasp on templates here: You never "call a template". You only ever *call a function*. A function can result from instantiating a function template, but to do so, you need to choose arguments for the template parameters. Sometimes these arguments can be deduced (via template argument deduction), but they're still there. So look again and consider how the first function template could possibly be instantiated to fit the call `foo(1,1)`, Only the second template works, by instantiating with `T1 = int` and `T2 = int`. – Kerrek SB Apr 30 '17 at 23:31
  • OK, now I think I get it. When I do it like foo(1,1) it chooses the first one. Thanks for the response. – Jakub Dąbek Apr 30 '17 at 23:34
  • Just to add to the previous comment, the 2nd template argument can be anything, not int specifically. – Jakub Dąbek Apr 30 '17 at 23:54
  • Actually I think in case of int there the call should be ambiguous. Nonetheless it compiles so am I missing something? – Jakub Dąbek May 01 '17 at 00:25
  • Obviously, if different compilers give different output, then one or both of them must be wrong. – Walter May 01 '17 at 11:53

1 Answers1

4

It is easy to see why this fails by simply deleting the second template, i.e. having a source file like

template<class T1, class T2>
void foo(T1, T1);

int main()
{
  foo(1,1);
}

This fails in Clang with the error

test.cpp:6:3: error: no matching function for call to 'foo'
  foo(1,1);
  ^~~
test.cpp:2:6: note: candidate template ignored: couldn't infer template argument
      'T2'
void foo(T1, T1);
     ^
1 error generated.

The compiler has no way to deduce the second template parameter T2.


If you delete the superfluous T2 from the first template and use this kind of source file

template<class T1>
void foo(T1, T1);

template<class T1, class T2>
void foo(T1, T2);

int main()
{
  foo(1,1);
}

The compiler will always choose the the first option (if T1 and T2 are the same of course) because it is more specialised.

Another option is to give T2 a default value. Then the first variant can also be instantiated from foo(1,1).

template<class T1, class T2 = void>
void foo(T1, T1);

Another interesting thing is this:

#include <iostream>

template<class T1, class T2>
void foo(T1, T1)
{ std::cout << "First choice!\n"; }

template<class T1, class T2>
void foo(T1, T2)
{ std::cout << "Second choice!\n"; }

int main()
{
  foo<int,int>(1,1);
}

This will output at runtime:

First choice!

I'm not entirely sure, but I believe this is because in the case of deduction (even though it is not carried out here), for the first variant the compiler would have to deduce only one type instead of two which makes it the more specialised option.

Henri Menke
  • 10,705
  • 1
  • 24
  • 42
  • I was messing around with that because I couldn't grasp the difference between overloading function templates and specialising. To overload don't the function templates have to have the same arguments? – Jakub Dąbek May 01 '17 at 00:07
  • @JakubDąbek See this question for details http://stackoverflow.com/a/22411782/1944004 – Henri Menke May 01 '17 at 00:09
  • Shouldn't the last example be ambiguous then? Both templates are the same and both functions have the same functionality. – Jakub Dąbek May 01 '17 at 00:21