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

时间:2018-01-31 13:41:06

标签: c++ templates language-lawyer function-pointers 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;
    }
};

我期待发生什么

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

问题

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

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

1 个答案:

答案 0 :(得分:4)

  

考虑到只有一个可以导致有效的模板实例化,实际上并不存在歧义。

但是有!你太快了。 +{-}()-/:'?,.采用模板参数,需要推导出该参数。但是你传递了一个重载集,并且无法推导出该参数。

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

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

[temp.deduct.type]p5

中对此进行了描述
  

由于关联的函数参数是函数,或者一组重载函数([over.over])以及以下一个或多个,因此无法进行参数推导的函数参数应用:   (5.5.1)

     
      
  • 多个函数与函数参数类型匹配(导致模糊推理),或

  •   
  • [...]

  •   

所以我们有一个非演绎的背景。现在怎么办?根据{{​​3}}:

  

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

我们已经完成了!