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

for*_*818 6 c++ templates function-pointers language-lawyer stl-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;
    }
};
Run Code Online (Sandbox Code Playgroud)

我期待发生什么

有两个可能的重载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);
                                                               ^
Run Code Online (Sandbox Code Playgroud)

如何解决它

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

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;
}
Run Code Online (Sandbox Code Playgroud)

问题

究竟是什么阻止了编译器选择"正确"的重载?考虑到只有一个可以导致有效的模板实例化,实际上并不存在歧义.

PS:请注意,这是C++ 98.在C++ 11及更高版本中,使用lambdas可以很容易地避免这个问题(感谢@appleapple指出这一点).

Rak*_*111 4

考虑到只有一个可以产生有效的模板实例化,因此并没有真正的歧义。

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

您可能认为 SFINAE 也适用于此,但事实并非如此。当用模板参数替换函数时会发生 SFINAE,但在其他地方不会发生。这里没有替换,因为由于重载集推导失败,编译器甚至无法到达该点。此外,SFINAE 适用于函数参数,而不适用于函数体。

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

这在[temp.deduct.type]p5中有描述:

无法进行参数推导的函数参数,因为关联的函数参数是一个函数或一组重载函数([over.over]),并且适用以下一个或多个:(5.5.1)

  • 多个函数与函数参数类型匹配(导致推论不明确),或者

  • [...]

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

[...]。如果模板参数仅在非推导上下文中使用并且未显式指定,则模板参数推导将失败。[...]

我们就完成了!