6

The Problem

Please bear with me, this is really just an example:

#include <algorithm>
#include <iterator>
struct foo {
    static int my_transform(int x) { return x;}
    static std::vector<int> my_transform(std::vector<int> x){
        std::vector<int> result;            
        std::transform(x.begin(),x.end(),std::back_inserter(result),my_transform);
        return result;
    }
};

What I expect to happen

There are two possible overloads for my_transform, but only one results in a well-formed template instantiation, while for the other the template instantiation is ill-formed. I would expect the ill-formed one to be discarded and the above to compile.

What really happens

 main.cpp:165:75: error: no matching function for call to
 ‘transform(std::vector<int>::iterator, std::vector<int>::iterator, 
 std::back_insert_iterator<std::vector<int> >, <unresolved overloaded function type>)’
   std::transform(x.begin(),x.end(),std::back_inserter(result),my_transform);
                                                               ^

How to fix it

Casting the function pointer to the right type resolves the ambiguity and it compiles:

static std::vector<int> foo::my_transform(std::vector<int> x){
    std::vector<int> result;
    typedef int (*my_transform_t)(int);     
    std::transform(x.begin(),
                   x.end(),
                   std::back_inserter(result),
                   static_cast<my_transform_t>(my_transform));
    return result;
}

The Question

What exactly prevents the compiler from choosing the "correct" overload? Considering that only one can result in a valid template instantiation there isnt really a ambiguity.

PS: Note that this is C++98. In C++11 and beyond, the problem can be easily avoided by using lambdas (thanks to @appleapple for pointing that out).

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • " I would expect the ill-formed one to be discarded and the above to compile." And there's your mistake! #helping – UKMonkey Jan 31 '18 at 13:44
  • In short: the cast is really the way to go in such a case. Or design in a way in which such a scenario wouldn't arise. – DeiDei Jan 31 '18 at 13:45
  • @UKMonkey yes I knew this was the weak point of my reasoning, but I dont really understand why it doesnt apply – 463035818_is_not_an_ai Jan 31 '18 at 13:45
  • @DeiDei ok I will remove the "how to avoid the cast" part, but I would still like to know what exactly happens here, though maybe too broad... – 463035818_is_not_an_ai Jan 31 '18 at 13:46
  • 1
    @tobi303 you can actually avoid the cast (in some way), just use a generic lambda `[](auto& x){return my_transform(x);}` or replace `auto` with `int`, of course. – apple apple Jan 31 '18 at 13:48
  • @appleapple sorry, forgot to mention C++98. If it was C++11 I wouldnt mess around with function pointers ;) – 463035818_is_not_an_ai Jan 31 '18 at 13:50
  • 2
    Related: https://stackoverflow.com/questions/2942426/how-do-i-specify-a-pointer-to-an-overloaded-function (don't get excited though - they largely cover what you say in your question about static casting); which I think answers your last question – UKMonkey Jan 31 '18 at 13:50
  • I'm not a language lawyer, but my gut tells me overload resolution kicks in at the call site. What happens in this case is: you pass a function pointer to std::transform and its type is already set in stone before any calls to it. So the function in the most inner scope is chosen (as evident by the example). – DeiDei Jan 31 '18 at 13:55
  • about fixing it, another simple solution (that also works with C++98) is to rename: ` static int my_transform(int x) { return x; }` as ` static int my_transform_func(int x) { return x; }` – Picaud Vincent Jan 31 '18 at 14:02
  • 1
    Just reading http://en.cppreference.com/w/cpp/language/overloaded_address It's got a direct example there using static_cast. – UKMonkey Jan 31 '18 at 14:16
  • @UKMonkey ups, should have done more research before ;). They even use the same algorithm as example.... – 463035818_is_not_an_ai Jan 31 '18 at 14:18

1 Answers1

4

Considering that only one can result in a valid template instantiation there isn't really a ambiguity.

But there is! You are just too quick. std::transform takes a template parameter and that parameter needs to be deduced. But you are passing it an overload set and the the parameter can't be deduced.

You might think that SFINAE also applies here too, but this is not the case. SFINAE happens when substituting template parameters for functions, but nowhere else. Here there is no substitution, because the compiler can't even reach that point because of the overload set deduction failure. Also, SFINAE applies to function parameters, not to the body of the function.

Basically: the compiler will not instantiate multiple possible templates and look which one is the only one that compiles. That would get complicatd quickly.

This is described in [temp.deduct.type]p5:

A function parameter for which argument deduction cannot be done because the associated function argument is a function, or a set of overloaded functions ([over.over]), and one or more of the following apply: (5.5.1)

  • more than one function matches the function parameter type (resulting in an ambiguous deduction), or

  • [...]

And so we have a non-deduced context. What now? According to [temp.deduct]p4:

[...]. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails. [...]

And we're done!

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • but I know I've seen somewhere a proposal that when an overloaded function is passed as an argument to a template paramter `T` then a functor will be created with overloaded `operator()` which will solve this exact issue. I can't find it now. – bolov Jan 31 '18 at 14:11
  • @bolov Are you sure about that? Because I have a feeling that this is not the case, as if you constrain the last parameter to be callable using a specified type. – Rakete1111 Jan 31 '18 at 14:18
  • I only have a gcc implementation of concepts https://godbolt.org/g/NgGNiz but I don't see this changing in the final version. – bolov Jan 31 '18 at 14:22
  • @bolov See my update quote: "[...] trial argument deduction is attempted using each of the members of the set." This must include checking constraints, no? I don't know what the "trial" is about though... – Rakete1111 Jan 31 '18 at 14:25
  • I might be wrong as I am not that good with the standard, but I think the paragraph doesn't apply here. `P` denotes the parameter, not the argument. I.e. the paragraph would apply to something like this `template auto foo(Ret (*P) (T))` The examples seem to back this up. – bolov Jan 31 '18 at 14:31
  • @bolov Oh yeah, oops. Fixed and thanks :) Well, we will have to see what clang and gcc do then :) I'm optimistic though – Rakete1111 Jan 31 '18 at 14:34