5

In the following template function with a parameter pack and a ReturnType, why is the compiler OK if I omit the last parameter ReturnType, whereas giving me an error (about ambiguity) if I explicitly give the the last type parameter.

Thanks.

#include <functional>
using namespace std;

template<typename... Args, typename ReturnType>
auto make_function(ReturnType(*p)(Args...))
    -> std::function<ReturnType(Args...)> {
  return {p};
}

int foo1(int x, int y, int z) { return x + y + z;}
float foo1(int x, int y, float z) { return x + y + z;}

int main() {
  auto f0 = make_function<int,int,int>(foo1); //OK
  //auto f1 = make_function<int,int,int,int>(foo1); //not OK
  // test33.cpp:15:48: error: no matching function for call to 
  // 'make_function(<unresolved overloaded function type>)'
  return 0;
}
thor
  • 21,418
  • 31
  • 87
  • 173
  • You don't need trailing return type for this example. – user3286380 Feb 13 '14 at 03:35
  • But why is giving the complete type generating an error? Args..., ReturnType count 4. – thor Feb 13 '14 at 03:40
  • I have no clue, this example mystifies me. In fact I don't even know why it's using `Args` = `int, int, int` and deducing `ReturnType`, instead of `Args` = `int, int` and `ReturnType` = `int`, when you do `make_function`. – user3286380 Feb 13 '14 at 03:43
  • same here. this is with gcc 4.8.1 (mingw.org) – thor Feb 13 '14 at 03:45
  • 2
    Since `Args` is variadic, are you sure it is even legal to put a non-variadic parameter after a variadic parameter? It would make more sense to put `ReturnType` before `Args` instead of after (though that would mean you then have to explicitly state the return type when using the template). – Remy Lebeau Feb 13 '14 at 03:45
  • 5
    @user: Variadic templates are greedy, they swallow every argument they can get. The 'not OK' case simply has `Args = [int, int, int, int]`. – Xeo Feb 13 '14 at 03:46
  • @Xeo So all template params after a variadic one can only be deduced, correct? – user3286380 Feb 13 '14 at 03:47
  • 4
    Yes. @Remy: It's OK as long as all parameters after the pack are deduced. – Xeo Feb 13 '14 at 03:47
  • @Xeo, thanks for the clarification. Is this behavior actually beneficial? i.e. are there occasions you would want to do this? – thor Feb 13 '14 at 03:49
  • @Xeo, the third parameter is int in one and float in another. Is this enough to differentiate them? – thor Feb 13 '14 at 03:50
  • It's not about benificial or not. How exactly do you expect the compiler to realize that the fourth argument is supposed to bind to `ReturnType`? (And nevermind my last comment, I thought both overloads had 3 `int` arguments for a second, which wouldn't actually ever work.) – Xeo Feb 13 '14 at 03:51
  • I don't know, someone showed this code to me. It took me quite a while to figure out what's going on. http://stackoverflow.com/questions/21738775/ – thor Feb 13 '14 at 03:52
  • @Xeo it seems like it should work, I mean putting `int, int, int, int` into the pattern `A..., B` would intuitively seem to make `A` hold `int, int, int` and `B` hold the last `int`, but I guess that is too complicated a behavior to mandate in the standard. TingL, when you explicitly specify template parameters, they are no longer deduced, so it doesn't look at the signature of the function to disambiguate. – user3286380 Feb 13 '14 at 03:55
  • Yes. what makes me wonder is the compiler behavior here. The only way to make it work is to drop the last parameter. Without giving the parameters, compiler fails for apparent ambiguity reasons. Giving all parameters fails as well. – thor Feb 13 '14 at 03:57
  • @TingL you don't have to drop the last parameter, it is deduced. If you don't specify any params, it fails because of another reason, that the function has _multiple_ good matches, rather than _no_ good matches which is why it fails in your "not OK" case. So it makes perfect sense, and to fix, just put the return type as the first param if you want to be able to disambiguate without making a cast. Great question btw. – user3286380 Feb 13 '14 at 03:58
  • @user3286380 good question indeed. subtle but *deep*. love that. – WhozCraig Feb 13 '14 at 04:01

1 Answers1

1

Credit to Xeo.

Putting a parameter after a parameter pack is a special case where deduction is forced. You cannot explicitly supply an argument to ReturnType. Therefore it goes looking for foo1( int, int, int, int ) and finds nothing.

By the way, if you want to defeat deduction, one trick is to hide the argument list by taking the address of the function: (&make_function<int,int,int,int>)(foo1). This causes Clang to complain specifically

candidate template ignored: couldn't infer template argument 'ReturnType'

and it ICEs GCC (but still prints a diagnostic pointing to the right line).

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421