Search code examples
c++templatesnon-type

Overloaded non-type template is ambiguous while non-templated function is ok


If we have a template function which takes a non-type parameter of type int or short the compiler complains about the ambiguity of the following call:

// Definition
template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }

// Usage
foo<0>(); // Ambiguous, int or short?

At first I wasn't surprised with this behaviour, the literal 0 could be an int or a short, but if we try this:

// Definition
void foo(int i)   { std::cout << "i: " << i << '\n'; }
void foo(short s) { std::cout << "s: " << s << '\n'; }

// Usage
foo(0); // "i: 0"!

The call to foo is unambiguous! it takes the int overload (even when the template version did not). Well, after thinking a little, this isn't a surprising behaviour, after all there's no way to specify a short literal so the compiler thinks that 0 is an int (this is the default behaviour AFAIK), in order to unambiguously call the short version of non-templated foo we can explicitly instantiate a short:

foo(0);        // "i: 0"
foo(short{0}); // "s: 0"

So i thought that this would unambiguate the templated version, but it did not:

foo<int{0}>();   // Ambiguous call, candidates are int and short versions
foo<short{0}>(); // Ambiguous call, candidates are int and short versions
call of overloaded 'foo()' is ambiguous
 foo<int{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short int S = 0]

call of overloaded 'foo()' is ambiguous
 foo<short{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short int S = 0]

The last thing I've tried was to use instances instead of literals:

template <int I>   void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }

void foo(int i)   { std::cout << "i: " << i << '\n'; }
void foo(short s) { std::cout << "s: " << s << '\n'; }

constexpr int i{1};
constexpr short s{5};

int main()
{
    foo(i); // "i: 1"
    foo(s); // "s: 5"
    foo<i>(); // Ambiguous! (expected "I: 1")
    foo<s>(); // Ambiguous! (expected "S: 5")
    return 0;
}

Without success, as you can se... So, what's the question?

  • Why the call to templated foo is ambiguous? (note that the no templated foo takes int version so is unambiguous).
  • Why the call to templated foo remains ambiguous even after specifying the type on the call? (note that the no templated foo works fine).

Thanks.


Solution

  • Here's what happens when you write f<0>().

    1. The compiler looks up f, and finds two function template declarations:

      template <int I>   void foo();
      template <short S> void foo();
      
    2. The compiler sees the explicit template argument list and attempts to substitute it into each function template declaration:

      template <int I>   void foo(); // with I = 0
      template <short S> void foo(); // with S = 0
      

      Substitution succeeds in both cases because 0 is an int, and can be converted to short, and the conversion is an allowed conversion in this context.

    3. After substitution, two candidate function specializations are produced. Both are viable. Overload resolution is then performed - and since the signature is identical and no tiebreaker applies, overload resolution fails and the call is ambiguous.

    The point here is that the normal overload resolution rules do not apply to template arguments. The conversions for template arguments are applied in an earlier stage, before the regular overload resolution takes place.