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

Execute a function as parameter in C++

本文关键字:函数 一个 参数 C++ 执行      更新时间:2023-10-16

假设我们有类Obj和这个main:

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

我想要一个通用函数来从所有对象中调用特定函数,如下面的伪代码。

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或类似的技巧可以做到这一点吗?

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

// 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风格的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);

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

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...。它在每个args...上运行一次f。奇怪的discard技巧包括制作一个所有0的数组并将其丢弃(优化器不会这样做(,以便生成一个上下文,在该上下文中...将完全按照我们的意愿进行操作。

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

如果您想从实现中分离接口,您可以用std::function<void(Obj&)>替换class FF&&,以获得适度的性能成本。

相关文章: