18

I declare two templates, the first converts the argument x from type T to type U and the second from type U to type T. If I call cast with 10, the compiler does not complain. I think both meet the requirements to be used and therefore there should be ambiguity, is that true? This code prints 10.

#include <iostream>

template<typename T, typename U>
U cast(T x) {
    return static_cast<U>(x);
}

template<typename T, typename U>
T cast(U x) {
    return static_cast<T>(x);
}

int main() {
    std::cout << cast<int,float>(10) << '\n';
}
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
wic
  • 362
  • 3
  • 14
  • If you change your code to `cast(10.0f)` the second overload would be called. And if you changed it to `cast(10.0)` the call would be ambiguous. – Praetorian Oct 02 '18 at 13:59

2 Answers2

24

When you use cast<int, float>, both templates are considered.

template<typename T=int,typename U=float>
U cast(T x);
template<typename T=int,typename U=float>
T cast(U x);

we then substitute:

template<typename T=int,typename U=float>
float cast(int x);
template<typename T=int,typename U=float>
int cast(float x);

at this point, there are no types to deduce. So we go to overload resolution.

In one case, we can take an int and convert to float when calling cast, and in the other we take a int and convert to int when calling cast. Note I'm not looking at all into the body of cast; the body is not relevant to overload resolution.

The second non-conversion (at the point of call) is a better match, so that overload is chosen.

If you did this:

std::cout << cast<int>(10) << "\n";

things get more interesting:

template<typename T=int,typename U=?>
U cast(T x);
template<typename T=int,typename U=?>
T cast(U x);

for the first one, we cannot deduce U. For the second one we can.

template<typename T=int,typename U=?>
U cast(int x);
template<typename T=int,typename U=int>
int cast(int x);

so here we have one viable overload, and it is used.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • what OP's code actually does is convert from int to float (verified in assembler), so what non-conversion? –  Oct 02 '18 at 14:03
  • 1
    @jakub_d In the overload resolution of the argument. The body of a function does not change which function is called. – Yakk - Adam Nevraumont Oct 02 '18 at 14:06
2

The instantiation is not ambiguous because the function argument exactly matches the first template parameter: the literal 10 is an int, which is also explicitly specified for the first template type.

You can make the instantiation ambiguous when the argument type does not match the explicitly specified types (thus, a conversion is necessary), e.g.

// error: call of overloaded 'cast<int, float>(unsigned int)' is ambiguous
std::cout << cast<int,float>(10u) << "\n";
lubgr
  • 37,368
  • 3
  • 66
  • 117