c++ - 如何使用 `std::function` 作为函数参数创建可变参数模板函数?

标签 c++ c++11 variadic-templates std-function

如何使用 std::function 创建一个可变参数模板函数作为接受可变数量参数的函数参数?我试图将问题减少到 MWE:

#include <functional>

template <class T> void run(std::function<void(T *)> fun, T *obj) { fun(obj); }

template <class T, class... Args>
void run_variadic(std::function<void(T *, Args...)> fun, T *obj, Args... args) {
  fun(obj, args...);
}

struct Foo {
  void bar() {}
};

int main() {
  Foo foo;
  std::function<void(Foo *)> fun = &Foo::bar;

  run(fun, &foo);                     // works
  run<Foo>(&Foo::bar, &foo);          // works
  run_variadic(fun, &foo);            // works
  run_variadic<Foo>(&Foo::bar, &foo); // does not compile
}

run_variadic 中可变参数模板参数的存在似乎使得无法直接使用成员函数指针调用它。 clang的报错信息如下:

main.cpp:21:3: error: no matching function for call to 'run_variadic'
  run_variadic<Foo>(&Foo::bar, &foo); // does not compile
  ^~~~~~~~~~~~~~~~~
main.cpp:6:6: note: candidate template ignored: could not match 'function<void (Foo *, type-parameter-0-1...)>' against 'void (Foo::*)()'
void run_variadic(std::function<void(T *, Args...)> fun, T *obj, Args&&... args) {
     ^
1 error generated.

关于如何修复 run_variadic 以便我不必通过额外的 std::function 对象有什么建议吗?

背景

我有一个类层次结构

template <class T> class Abstract { ... };
class UnrelatedStuff { ... };
class Derived : public Abstract<UnrelatedStuff> { ... };

有多个 Derived 类,它们都必须实现一个或多个方法来遍历一系列元素。循环看起来像

#pragma omp parallel for
for (ZFSId i = begin; i != end; ++i) {
  callMemFun(i, and, other, args);
}

所有循环都应该是 OpenMP 加速的。我希望在使用循环的 Derived 的每个方法中分解出加速器的东西,而不是重复它,这样我只需要更改一个地方,例如OpenMP 将切换到 OpenACC。

因此,我正在寻找一种将循环(及其装饰)放入其自己的函数中的方法。将它移动到 Abstract 基类也不是一个选项,因为循环对性能至关重要,我不能在每个循环迭代中调用抽象函数。

最佳答案

抽象出函数对象几乎总是更好:

template <class Functor, class... Args>
void run(Functor&& f, Args&&... args) {
  f(std::forward<Args>(args)...);
}

这使您可以在调用站点做正确的事情:

// function object is a lambda that binds to a member function:
run([&](auto... args) { foo.bar(args...); } /*, bar takes no args...*/);

我更喜欢 lambda 而不是 std::functionstd::bind 但如果它们已经可用,您也可以使用它们:

run(std::function<void(Foo *)>{&Foo::bar}, &foo);
run(std::bind(&Foo::bar, &foo));
run(std::mem_fn(&Foo::bar), foo);

我在下面提供了一个完整的示例程序。

您现在已经用关于您正在尝试做的事情的新信息编辑了问题。

我很确定你不想这样做,因为像 parallel for 这样的 OpenMP/OpenACC pragmas 通常需要额外的注释来提供合理的性能,它们取决于你是什么正是在调用站点尝试做的。

不过,如果您真的想走这条路,您可以编写您的自己的 for_each 算法并根据 进行调度ExecutionAgent(参见 N3874N3731 )。如果 OpenMP、TBB、OpenACC 并行任务太慢,您还可以轻松地提供基于例如像这样的 ExecutionPolicy:

template<class RandomAccessRange, class Functor, 
         class ExecutionPolicy = execution::serial_t>
void for_each(RandomAccessRange&& r, Functor&& f, 
              ExecutionPolicy&& ex = ExecutionPolicy{}) {
  detail::for_each_(std::forward<RandomAccessRange>(r), 
                    std::forward<Functor>(f), 
                    std::forward<ExecutionPolicy>(ex));
}

然后您可以为每个执行策略实现 for_each_ 的重载,例如:

namespace detail {

template<class RandomAccessRange, class Functor>
void for_each(RandomAccessRange&& r, Functor&& f, execution::serial_t) {
  boost::for_each(std::forward<RandomAccessRange>(r), std::forward<Functor>(f));
}

template<class RandomAccessRange, class Functor>
void for_each(RandomAccessRange&& r, Functor&& f, execution::openmp_t) {
  #pragma omp parallel for
  for (auto&& v : r) { f(v); } 
}

template<class RandomAccessRange, class Functor>
void for_each(RandomAccessRange&& r, Functor&& f, execution::openacc_t) {
  #pragma acc parallel for
  for (auto&& v : r) { f(v); } 
}

template<class RandomAccessRange, class Functor>
void for_each(RandomAccessRange&& r, Functor&& f, execution::tbb_t) {
  tbb::parallel_for_each(std::begin(std::forward<RandomAccessRange>(r)),
                         std::end(std::forward<RandomAccessRange>(r)),
                         std::forward<Functor>(f)); 
}

}  // namespace detail

请注意,ExecutionPolicy 只是一个标记,即:

namespace execution {
  struct serial_t {}; static const constexpr serial_t serial{};
  struct openmp_t {}; static const constexpr openmp_t openmp{};
  struct openacc_t {}; static const constexpr openacc_t openacc{};
  struct tbb_t {}; static const constexpr tbb_t tbb{};
}  // namespace execution

这将至少为您提供一个高效的 TBB 后端,即使 OpenMP/OpenACC 性能充其量只是中等水平。您可以查看他们使用 OpenMP 的 libstdc++ 的并行实现。他们的 for_each 算法超过 1000 行代码并使用工作窃取。

完整示例程序:

#include <functional>

template <class Functor, class... Args>
void run(Functor&& f, Args&&... args) {
  f(std::forward<Args>(args)...);
}

struct Foo { void bar() {} };

int main() {
  Foo foo;

  run([&](auto... args) { foo.bar(args...); } /*, bar takes no args*/);
  run(std::function<void(Foo *)>{ &Foo::bar}, &foo);
  run(std::bind(&Foo::bar, &foo));
  run(std::mem_fn(&Foo::bar), foo);
}

关于c++ - 如何使用 `std::function` 作为函数参数创建可变参数模板函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22965647/

相关文章:

c++ - 模板类型参数未按要求扩展

c++ - 带有 IWebBrowser 组件的 CDialog,退格键不起作用

python - 错误的 ctypes 分配

c++ - 未对齐访问导致 ARM Cortex-M4 出错

C++11 模板函数别名与包装器

c++ - 如何使用可变参数模板作为 std::type_index 映射的键?

c++ - Qt C++ 未处理的异常堆栈跟踪

c++ - 带构造函数的模板 SFINAE

c++ - std::bind 需要默认构造函数

c++ - 解包到可变参数构造函数