16

Given this code, how does template argument deduction decide what to do for the last function call?

#include <iostream>

template<typename Ret, typename... Args>
Ret foo(Args&&...) {
    std::cout << "not void\n";
    return {};
}

template<typename... Args>
void foo(Args&&...) {
    std::cout << "void\n";
}

int main() {
    foo(3, 'a', 5.4);            //(1): prints "void"
    foo<int, char>(3, 'a', 5.4); //(2): prints "void"
    foo<int>('a', 5.4);          //(3): prints "not void"
    foo<int>(3, 'a', 5.4);       //(4): prints "not void"
}

(1) seems pretty straightforward. It can't deduce a return type, so the void version is used.

(2) explicitly states some of the arguments' types. The first template argument matches the first argument, the second template argument matches the second argument, and the third template argument is deduced. If the int was used for the return type, the char wouldn't match the first argument.

(3) does the same thing as (2), but the first types do not match. Therefore, that must be deduced as the return type and the two arguments used to deduce the two Args arguments.

(4) seems ambiguous. It explicitly specifies a template argument, just like (2) and (3). The template argument matches the argument, just like (2). However, it does not use that as the first and deduce the other two, but rather use the explicit template argument as the return type and deduce all three Args arguments.


Why does (4) seem to half follow (2), but then use the other version? The best guess I have is that the single template parameter being filled in is a better match than just the parameter pack. Where does the standard define this behaviour?

This was compiled using GCC 4.8.0. For convenience, here's a test run on Coliru.

On Clang 3.1, however, (4) does not compile, due to ambiguity (see comments). This opens up the possibility that one of these two compilers has a bug. Making this possibility more probable is the fact that the Visual Studio 2012 November CTP compiler gives the same result as Clang, with (4) being ambiguous.

chris
  • 60,560
  • 13
  • 143
  • 205
  • I don't know the answer but if I had to guess, #4 fits both first and second template signatures, and the compiler just chooses the first one based on precedence, either because the template definition came first or because it's more specialized. Again this is just my opinion. – Bee May 07 '13 at 01:56
  • @bee I see no specialization in this question. Overloading sure, but no specialization. – Yakk - Adam Nevraumont May 07 '13 at 02:19
  • with clang 3.1 this does not compile – aaronman May 07 '13 at 02:49
  • @aaronman, Thank you for testing that. I was going to until I saw that LWS was still down. Is it just (4) that doesn't compile, or are there other errors? – chris May 07 '13 at 02:53
  • Yes just 4 because of ambiguity, it's possible that GCC has bugs in it – aaronman May 07 '13 at 02:54
  • @aaronman, That really helps, thank you. I'll add that to the question for clarity. – chris May 07 '13 at 02:58
  • @aaronman, I didn't think to try this with MSVC yet, either (possibly due to the C++11, even though the CTP supports it). It gives the same result as Clang. With that, I should probably be getting to bed now, as I have an AP exam in the morning. I guess I'll be looking forward to whether anything turns up by then. – chris May 07 '13 at 03:02
  • Really curious why `foo(1, 'a', 5.4);` gives "void" but `foo('a', 'a', 5.4);` gives "not void" even those parameters are totally same. – silvesthu May 07 '13 at 03:17
  • @silvesthu: Because now `foo('a', 'a', 5.4)` is viable, since the first type in `Args` is specified as `char` through explicit template arguments, and deduction yields the same type. Before, deduction yielded `int`. And now, implicit conversion sequences are considered - and `char -> char` is the identity conversion, while `char -> int` is promotion. – Xeo May 07 '13 at 06:29

1 Answers1

4

I believe Clang to be correct (still in 3.3 SVN), the call in (4) is ambiguous according to partial ordering rules - both function template are viable and neither is more specialized than the other.

Note that GCC can be coerced into giving the same output by transforming the variadic packs into single parameters:

#include <iostream>

template<typename Ret, typename T>
Ret foo(T&&) {
    std::cout << "not void\n";
    return {};
}

template<typename T>
void foo(T&&) {
    std::cout << "void\n";
}

int main() {
    foo<int>(3);
}

Output:

main.cpp: In function 'int main()':  
main.cpp:15:15: error: call of overloaded 'foo(int)' is ambiguous

Live example.

Xeo
  • 129,499
  • 52
  • 291
  • 397