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


137

我想将重载函数传递给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);
    }
};

我希望编译器f()按迭代器类型解析。显然,它(GCC 4.1.2)没有做到这一点。那么,如何指定f()我想要的呢?


Answers:


137

您可以根据函数指针类型所隐含的函数签名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)); 

或者,您也可以执行以下操作:

// 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

如果f是成员函数,则需要使用mem_fun,或者针对您的情况,使用Dobb博士文章中提供的解决方案


1
谢谢!但是,我仍然有一个问题,可能是由于该事实f()是一个班级的成员(请参见上面的编辑示例)
davka 2010年

9
@the_drow:第二个方法实际上要安全得多,如果其中一个重载消失了,第一个方法会静默给出未定义的行为,而第二个方法在编译时会发现问题。
Ben Voigt

3
@BenVoigt嗯,我在vs2010上进行了测试,找不到在编译时static_cast无法捕获问题的情况。它给C2440提供了“范围内此名称与目标类型都不匹配的功能”。你能澄清一下吗?
内森·蒙特里昂

5
@Nathan:我可能在想reinterpret_cast。我最经常看到用于此目的的C样式转换。我的规则是,对函数指针进行强制转换是危险且不必要的(如第二个代码片段所示,存在隐式转换)。
Ben Voigt 2012年

3
对于成员函数:std::for_each(s.begin(), s.end(), static_cast<void (A::*)(char)>(&A::f));
sam-w

29

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

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

或对lambda参数使用decltype:

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

使用多态lambda(C ++ 14):

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

或通过消除重载来消除歧义(仅适用于自由功能):

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

void scan(const std::string& s)
{
    std::for_each(s.begin(), s.end(), f_c);
}

为lambdas欢呼!确实,这是解决重载问题的绝佳解决方案。(我也想到了这一点,但决定将其排除在我的回答之外,以免使水变得浑浊。)
aldo 2014年

相同结果的更多代码。我认为这不是lambda的目的。
托马什Zato -恢复莫妮卡

@TomášZato区别在于此答案有效,而被接受的答案无效(例如OP发布的示例-您还需要使用mem_fnbind,而且BTW也是C ++ 11)。另外,如果我们想得到真正的书呆子,[&](char a){ return f(a); }则为28个字符,static_cast<void (A::*)(char)>(&f)为35个字符。
milleniumbug

1
@TomášZato你去那里coliru.stacked-crooked.com/a/1faad53c4de6c233不知道如何使这更清楚
milleniumbug

18

为什么不起作用

我希望编译器f()按迭代器类型解析。显然,它(gcc 4.1.2)没有做到这一点。

如果真是那样,那就太好了!但是,for_each是一个函数模板,声明为:

template <class InputIterator, class UnaryFunction>
UnaryFunction for_each(InputIterator, InputIterator, UnaryFunction );

模板推导需要UnaryFunction在调用时选择一种类型。但是f没有特定的类型-这是一个重载函数,有许多fs都有不同的类型。目前尚无办法for_each说明模板推导过程f,因此模板推导完全失败。为了使模板推导成功,您需要在呼叫站点上做更多的工作。

修复它的通用解决方案

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

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

键入很多,但是这类问题经常烦人地出现,因此我们可以将其包装在一个宏中(叹息):

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

然后使用它:

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

这将完全符合您希望编译器的工作-对名称f本身执行重载解析,然后做正确的事情。不管f是自由函数还是成员函数,这都将起作用。


7

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

for ( int i = 0; i < s.size(); i++ ) {
   f( s[i] );
}

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


2
可能,但它很无聊:)而且,如果我想使用迭代器来避免[]运算符,则它会变得更长……
davka 2010年

3
@Davka无聊是我们想要的。同样,如果您担心的话,迭代器通常不会比使用op [更快(可能会更慢)。

7
对于循环,算法应该更可取,因为它们不易出错,并且有更好的优化机会。在某处有一篇文章……在这里:drdobbs.com/184401446
AshleysBrain,2010年

5
@Ashley直到我看到“不太容易出错”的一些客观统计数据,我才认为没有必要相信它。Meyers在本文中似乎在谈论使用迭代器的循环-我说的是不使用迭代器的循环的效率-我自己的基准测试表明,优化后这些循环的速度略快-当然不会慢。

1
我来了,我也发现您的解决方案要好得多。
peterh-恢复莫妮卡

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

4

如果您不介意使用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; }

(适用于成员函数;应该很明显地对其进行修改以使其适用于独立函数,并且应该能够提供两个版本,并且编译器会为您选择合适的版本。)

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


OP的问题是无法将重载名称传递给功能模板,而您的解决方案涉及将重载名称传递给功能模板?这是完全一样的问题。
巴里

1
@Barry不一样的问题。在这种情况下,模板参数推导成功。它可以工作(有一些小的调整)。
Oktalist,2016年

@Oktalist因为您正在提供R,所以不会被推导。在此答案中也没有提及。
巴里

1
@Barry我没有提供R,我正在提供ArgsR并被T推导。确实,答案可以得到改善。(T尽管在我的示例中没有,因为它不是指向成员的指针,因为它不适用于std::for_each。)
Oktalist,2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.