如何指定指向重载函数的指针?

dav*_*vka 128 c++ stl

我想将重载函数传递给std::for_each()算法.例如,

class A {
    void f(char c);
    void f(int i);

    void scan(const std::string& s) {
        std::for_each(s.begin(), s.end(), f);
    }
};
Run Code Online (Sandbox Code Playgroud)

我期望编译器f()通过迭代器类型来解析.显然,它(GCC 4.1.2)没有这样做.那么,我该如何指定f()我想要的?

In *_*ico 129

您可以根据函数指针类型隐含的函数签名static_cast<>()来指定使用哪个f:

// Uses the void f(char c); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(char)>(&f));
// Uses the void f(int i); overload
std::for_each(s.begin(), s.end(), static_cast<void (*)(int)>(&f)); 
Run Code Online (Sandbox Code Playgroud)

或者,您也可以这样做:

// The compiler will figure out which f to use according to
// the function pointer declaration.
void (*fpc)(char) = &f;
std::for_each(s.begin(), s.end(), fpc); // Uses the void f(char c); overload
void (*fpi)(int) = &f;
std::for_each(s.begin(), s.end(), fpi); // Uses the void f(int i); overload
Run Code Online (Sandbox Code Playgroud)

如果f是成员函数,那么您需要使用mem_fun或者根据您的情况使用Dr. Dobb的文章中提供的解决方案.

  • @the_drow:第二种方法实际上更安全,如果其中一个重载消失,第一种方法默默地给出未定义的行为,而第二种方法在编译时捕获问题. (9认同)
  • @Nathan:我有可能想到`reinterpret_cast`.我经常看到用于此的C风格演员表.我的规则只是对函数指针的强制转换是危险且不必要的(如第二个代码片段所示,存在隐式转换). (4认同)
  • @BenVoigt嗯,我在vs2010上测试了这个,但是找不到static_cast在编译时不能解决问题的情况.它为C2440提供了"在范围内没有此名称的功能与目标类型匹配".你能澄清一下吗? (3认同)
  • 对于成员函数:`std :: for_each(s.begin(),s.end(),static_cast <void(A ::*)(char)>(&A :: f));` (3认同)

mil*_*bug 25

Lambdas救援!(注意:需要C++ 11)

std::for_each(s.begin(), s.end(), [&](char a){ return f(a); });
Run Code Online (Sandbox Code Playgroud)

或者使用decltype作为lambda参数:

std::for_each(s.begin(), s.end(), [&](decltype(*s.begin()) a){ return f(a); });
Run Code Online (Sandbox Code Playgroud)

多态lambda(C++ 14):

std::for_each(s.begin(), s.end(), [&](auto a){ return f(a); });
Run Code Online (Sandbox Code Playgroud)

或者通过删除重载消除歧义(仅适用于免费功能):

void f_c(char i)
{
    return f(i);
}

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}
Run Code Online (Sandbox Code Playgroud)

  • 更多代码可获得相同结果。我认为这不是 lambda 的用途。 (2认同)
  • @TomášZato 的区别在于这个答案有效,而接受的答案则无效(对于 OP 发布的示例 - 您还需要使用 `mem_fn` 和 `bind`,顺便说一句,它们也是 C++11)。另外,如果我们想要变得真正迂腐`[&amp;](char a){ return f(a); }` 是 28 个字符,而 `static_cast&lt;void (A::*)(char)&gt;(&amp;f)` 是 35 个字符。 (2认同)

Bar*_*rry 15

为什么不起作用

我期望编译器f()通过迭代器类型来解析.显然,它(gcc 4.1.2)没有这样做.

如果是这样的话会很棒!但是,for_each是一个函数模板,声明为:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );
Run Code Online (Sandbox Code Playgroud)

模板推导需要UnaryFunction在呼叫点选择类型.但是f没有特定的类型 - 它是一个重载函数,有很多fs每个都有不同的类型.目前没有办法for_each通过说明f它想要的内容来帮助模板扣除过程,因此模板扣除完全失败.为了使模板扣除成功,您需要在呼叫站点上做更多工作.

修复它的通用解决方案

在这里跳了几年,然后是C++ 14.而不是使用static_cast(允许模板推导通过"修复" f我们想要使用的成功,但需要您手动执行重载解析以"修复"正确的),我们希望使编译器为我们工作.我们想要f一些args.以最通用的方式,这是:

[&](auto&&... args) -> decltype(auto) { return f(std::forward<decltype(args)>(args)...); }
Run Code Online (Sandbox Code Playgroud)

键入的内容很多,但是这种问题经常出现,所以我们可以将它包装在宏(叹息)中:

#define AS_LAMBDA(func) [&](auto&&... args) -> decltype(func(std::forward<decltype(args)>(args)...)) { return func(std::forward<decltype(args)>(args)...); }
Run Code Online (Sandbox Code Playgroud)

然后使用它:

void scan(const std::string& s) {
    std::for_each(s.begin(), s.end(), AS_LAMBDA(f));
}
Run Code Online (Sandbox Code Playgroud)

这将完全按照您希望的编译器执行 - 对名称f本身执行重载解析并执行正确的操作.无论f是自由函数还是成员函数,这都可以工作.


小智 6

不是回答您的问题,而是我是唯一找到的人

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}
Run Code Online (Sandbox Code Playgroud)

for_each在这种情况下,比in silico建议的替代方案更简单,更短?

  • 对于循环,算法应该更可取,因为它们不易出错,并且有更好的优化机会。在某处有一篇文章……在这里:http://www.drdobbs.com/184401446 (7认同)
  • @Ashley直到我看到关于“容易出错”的一些客观统计数据之前,我认为没有必要相信它。Meyers在本文中似乎在谈论使用迭代器的循环-我说的是不使用迭代器的循环的效率-我自己的基准测试表明,优化后这些循环略快-当然不会慢。 (4认同)
  • @Davka无聊是我们想要的。同样,如果您担心的话,迭代器通常不会比使用op [更快(可能会更慢)。 (3认同)
  • 可能,但它很无聊:)而且,如果我想使用迭代器来避免[]运算符,则它会变得更长... (2认同)
  • -1:至少把该死的东西写正确!使用正确的类型 (size_t) 并缓存大小:`for(size_t i=0,n=s.size();i&lt;n;++i){f(s[i]);}` 或使用for 循环的范围基数:`for(const auto&amp;item:s){f(item);}` (2认同)

Mat*_*hew 6

如果您不介意使用 C++11,这里有一个聪明的助手,它类似于(但不那么难看)静态转换:

template<class... Args, class T, class R>
auto resolve(R (T::*m)(Args...)) -> decltype(m)
{ return m; }

template<class T, class R>
auto resolve(R (T::*m)(void)) -> decltype(m)
{ return m; }
Run Code Online (Sandbox Code Playgroud)

(适用于成员函数;如何修改它以适用于独立函数应该是显而易见的,并且您应该能够提供两个版本,编译器将为您选择正确的版本。)

感谢 Miro Knejp 的建议:另请参阅https://groups.google.com/a/isocpp.org/d/msg/std-discussion/rLVGeGUXsK0/IGj9dKmSyx4J


ald*_*ldo 5

这里的问题似乎不是过载解析,而是实际上模板参数的推导。虽然@In silico 的出色答案通常会解决一个模棱两可的重载问题,但处理std::for_each(或类似方法)时最好的解决方法是显式指定其模板参数

// Simplified to use free functions instead of class members.

#include <algorithm>
#include <iostream>
#include <string>

void f( char c )
{
  std::cout << c << std::endl;
}

void f( int i )
{
  std::cout << i << std::endl;
}

void scan( std::string const& s )
{
  // The problem:
  //   error C2914: 'std::for_each' : cannot deduce template argument as function argument is ambiguous
  // std::for_each( s.begin(), s.end(), f );

  // Excellent solution from @In silico (see other answer):
  //   Declare a pointer of the desired type; overload resolution occurs at time of assignment
  void (*fpc)(char) = f;
  std::for_each( s.begin(), s.end(), fpc );
  void (*fpi)(int)  = f;
  std::for_each( s.begin(), s.end(), fpi );

  // Explicit specification (first attempt):
  //   Specify template parameters to std::for_each
  std::for_each< std::string::const_iterator, void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< std::string::const_iterator, void(*)(int)  >( s.begin(), s.end(), f );

  // Explicit specification (improved):
  //   Let the first template parameter be derived; specify only the function type
  std::for_each< decltype( s.begin() ), void(*)(char) >( s.begin(), s.end(), f );
  std::for_each< decltype( s.begin() ), void(*)(int)  >( s.begin(), s.end(), f );
}

void main()
{
  scan( "Test" );
}
Run Code Online (Sandbox Code Playgroud)