将函数指针传递给算法时选择正确的重载

Pick the right overload when passing function pointer to algorithm

问题

请耐心等待,这真的只是一个例子:

#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;
    }
};

我期望发生的事情

my_transform 有两种可能的重载,但只有一种会导致格式正确的模板实例化,而另一种会导致模板实例化格式错误。我希望格式错误的被丢弃,上面的编译。

到底发生了什么

 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);
                                                               ^

如何修复

将函数指针转换为正确的类型解决了歧义并编译:

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;
}

问题

究竟是什么阻止了编译器选择 "correct" 重载?考虑到只有一个可以导致有效的模板实例化,所以真的没有歧义。

PS:注意这是C++98。在 C++11 及更高版本中,使用 lambda 可以轻松避免该问题(感谢@appleapple 指出)。

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

但是有!你只是太快了。 std::transform 采用模板参数,需要推导该参数。但是您向它传递了一个重载集,并且无法推导该参数。

你可能认为SFINAE也适用于此,但事实并非如此。 SFINAE 在将模板参数替换为函数时发生,但在其他任何地方都不会发生。这里没有替换,因为重载集推演失败,编译器连那个点都达不到。此外,SFINAE 适用于函数参数,而不适用于函数体。

基本上:编译器不会实例化多个可能的模板并查看哪一个是唯一编译的模板。那会很快变得复杂。

这在[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

  • [...]

所以我们有一个非推导的上下文。现在怎么办?根据[temp.deduct]p4:

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

大功告成!