在C ++中执行函数作为参数

时间:2016-10-06 13:14:44

标签: c++ templates c++11 lambda

假设我们有班级Obj和主要版本:

class Obj
{
   public:
     void func1(int n) {}
     void func2(std:string n) {}
};

std::vector<Obj> retrieveObjs()
{
    std::vector<Obj> result;
    // ...
    return result;
}

int main()
{
    // Call func1 for all obj
    {
      auto objs = retrieveObjs();  
      for (auto& obj : objs)       
      {
         obj.func1(100);
      }
    }

    // Call func2 for all obj
    {
      auto objs = retrieveObjs();  
      for (auto& obj : objs)       
      {
         obj.func2("xxx");
      }
    }
    return 0;
}

我希望有一个泛型函数来调用所有objs中的特定函数,如下面的伪代码。

void invokeAll(FUNCTION f, PARAM p)   // pseudocode
{
   auto objs = retrieveObjs();
   for (auto& obj : objs)       
   {
     obj.f(p); 
   }
}

int main() // pseudocode
{
    invokeAll(func1, 100);
    invokeAll(func2, "xxx");
}

我不知道如何替换FUNCTIONPARAM以使其发挥作用。

使用template / lambda / for_each或类似的技巧可以做到吗?

2 个答案:

答案 0 :(得分:4)

您所描述的是指向成员函数的指针的一个很好的用例,其语法如下所示:

// get the pointer to the function.
auto funPtr = &Obj::func1;

Obj obj;

// call the method using the function pointer
obj.(*funPtr)();

在您的情况下,您可以将函数指针作为参数接收,将参数作为包接收。

// F is the type of the function pointer.
// As arguments and return type of `f` can change, so it's type `F` can.
template<typename F, typename... Args>
void invokeAll(F f, Args... args) {
    for (auto&& obj : retrieveObjs()) {
      // We call member `f` with `obj`
      // We expand the pack `args` to send it as multiple arguments
      obj.(*f)(args...); 
    }
}

您将能够以类似的方式调用该函数:

// Notice the member function pointer syntax
invokeAll(&Obj::func1, 100);

// Work with multiple arguments, [100, "test"] will be packed into `args`
invokeAll(&Obj::func2, 100, "test");

在C ++ 17中,使用std::invoke,您可以通过允许以Obj作为参数的任何类型的函数来进一步概括您的情况:

template<typename F, typename... Args>
void invokeAll(F f, Args... args) {
    for (auto&& obj : retrieveObjs()) {
      // invoke function `f` with `obj` as it's object and `args` as parameter.
      std::invoke(f, obj, args...); 
    }
}

如果你现在想要支持更多类型的功能,包括lambdas,你可以使用void_t style sfinae:

// The compiler will pick this function if `obj.(*f)(args...)` can compile
template<typename F, typename... Args>
auto invokeAll(F f, Args... args) -> void_t<decltype(std::declval<Obj>().(*f)(args...))> {
    //                   Here's the constraint ------^
    for (auto&& obj : retrieveObjs()) {
      obj.(*f)(args...); 
    }
}

// The compiler will pick this function if `f(obj, args...)` can compile
template<typename F, typename... Args>
auto invokeAll(F f, Args... args) -> void_t<decltype(f(std::declval<Obj>(), args...))> {
    //                   Here's the constraint ------^
    for (auto&& obj : retrieveObjs()) {
      f(obj, args...); 
    }
}

void_t定义如下:

template<typename...>
using void_t = void;

然后,通过它,你也可以解锁这个语法:

invokeAll([](Obj& obj, int a){
    // this block will be called for each `obj` in `retrieveObjs`
}, 100);

如果您也想支持不可复制的类型,请查找完美转发

答案 1 :(得分:1)

template<class F, class R>
void invoke_on_range( F&& f, R&& r ) {
  std::for_each( r.begin(), r.end(), std::forward<F>(f) );
}

这需要一个范围并在范围的每个元素上调用lambda。

int main() {
  invoke_on_range( [](Obj& obj){ obj.func1(100); }, retrieveObjs() );
  invoke_on_range( [](Obj& obj){ obj.func2("xxx"); }, retrieveObjs() );
}

有一些样板来编写lambda,但结构不是你的问题。

我发现这有时也很有用:

template<class F, class...Args>
void invoke_on_each( F&& f, Args&&...args ) {
  using discard=int[];
  (void)discard{0,(void(
    f( std::forward<Args>(args) )
  ),0)...};
}

这需要一个lambda f和一组args...。它在每个f上运行一次args...。奇怪的discard技巧涉及制作所有0的数组并抛弃它(优化器不会这样做),以便生成...完全符合我们要求的上下文

隐藏您在retrieveObjs上运行的事实似乎不值得编写另一个包装函数,但也可以这样做。

如果要将界面与实现分开,可以将class FF&&替换为std::function<void(Obj&)>,以获得适度的性能费用。