Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 639
PaperBirdMaster Avatar asked Mar 05 '15 10:03

PaperBirdMaster


2 Answers

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.

like image 77
T.C. Avatar answered Nov 10 '22 00:11

T.C.


The compiler error message is missleading*. You would intuitively think that it means that "The function invocation is ambigious!", but in reality the compilation fails in an earlier stage, the definition of the specialized function is not even generated at that point.

What it really means is this: "The function specialization is ambigious!"

Lets see how this compiles:

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

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}

The first step of the compilation is the template specialization.

Step 1: The compiler realizes that foo<0> is a template specialization, and generates a function declaration accordingly:

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

template<>
void foo<0>();

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}

Step 2: The compiler relizes that the function is actually invoked (This seems obvious in this case, but it is less obvious when you have a class template.), and generates a definition:

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

template<>
void foo<0>();

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}

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

Step 3: Now you have a callable function, the compilation continues normally.

Lets try to follow the same steps in your case:

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

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}

Step 1: Generating the function declaration:

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

template<>
void foo<0>();

int main(int argc, char* argv[])
{
    foo<0>();
    return 0;
}

And at this point the compilation fails, because the specialized declaration of foo is ambigious. If you want proof, try compiling this code:

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

template<>
void foo<0>();

int main(int argc, char* argv[])
{
    return 0;
}

You will get the same error message without the function invocation!

Update

So the takeaway is that everything translates to specialized function declarations. So no matter if you write foo<int{0}> of foo<short{0}>, the compiler will generate template<> void foo<0>(); for both. The explicit types will be ignored. (Thats why its really important that they are constexpr-s.)

Update As T.C. pointed out in his comment, in the standard (413rd page in the PDF) there is a very similar example:

[Example: In the following example, assuming a signed char cannot represent the value 1000, a narrowing conversion (8.5.4) would be required to convert the template-argument of type int to signed char, therefore substitution fails for the second template (14.3.2).

 template <int> int f(int);
 template <signed char> int f(int);
 int i1 = f<1000>(0); // OK
 int i2 = f<1>(0); // ambiguous; not narrowing

—end example]


*The error message is completely correct. Might not be particularly intuitive, but it reflects the procedure specified in the standard. – T.C.

like image 32
Gábor Angyal Avatar answered Nov 10 '22 01:11

Gábor Angyal