如何用`std :: function`作为函数参数创建一个可变参数模板函数?

Mic*_*per 11 c++ variadic-templates c++11 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 Code Online (Sandbox Code Playgroud)

看起来仅仅存在可变参数模板参数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 Code Online (Sandbox Code Playgroud)

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

背景

我有一个类层次结构

template <class T> class Abstract { ... };
class UnrelatedStuff { ... };
class Derived : public Abstract<UnrelatedStuff> { ... };
Run Code Online (Sandbox Code Playgroud)

有多个Derived类都必须实现一个或多个方法来循环一系列元素.循环看起来像

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

所有循环都应该是OpenMP加速的.我希望将加速器内容考虑在内并且不会在每个Derived使用循环的方法中重复,因此我只需要更改一个位置,例如OpenMP将切换到OpenACC.

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

gnz*_*lbg 14

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

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

这允许您在呼叫站点做正确的事情:

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

我更喜欢lambda std::function或者std::bind你也可以使用它们,如果它们已经可用:

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

我在下面提供完整的示例程序.

您现在已经使用有关您要执行的操作的新信息编辑了问题.

我很确定你不想这样做,因为OpenMP/OpenACC pragma parallel for通常需要额外的注释来提供合理的性能,而且它们取决于你在呼叫站点上正在尝试做什么.

尽管如此,如果你真的想要走这条路线,你可以根据自己的 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));
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以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
Run Code Online (Sandbox Code Playgroud)

请注意,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
Run Code Online (Sandbox Code Playgroud)

至少会为您提供高效的TBB后端,即使OpenMP/OpenACC性能最佳也是平庸的.您可以查看libstdc ++的并行实现,它们使用OpenMP.他们的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);
}
Run Code Online (Sandbox Code Playgroud)


Okt*_*ist 5

要回答您对上一个答案的评论,可以调整该答案以按照您要求的方式支持指向成员函数的指针。上一个答案已经适用于所有可调用对象,但不能直接使用指向成员函数的指针,因为它们不能用通常的f(args)语法调用。以下版本使用标签调度来区分指向成员函数的指针和传统的可调用对象,并应用适合每种情况的调用语法。

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

template <class Functor, class Arg0, class... Args>
void run_helper(std::true_type, Functor f, Arg0&& arg0, Args&&... args)
{
    (std::forward<Arg0>(arg0).*f)(std::forward<Args>(args)...);
}

template <class Functor, class... Args>
void run(Functor f, Args&&... args)
{
    run_helper(typename std::is_member_pointer<Functor>::type(),
               f, std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)

这可以以与上一个答案相同的方式使用,但也支持直接传递指向成员函数的指针:

run(&Foo::bar, foo);
Run Code Online (Sandbox Code Playgroud)

它甚至适用于作为模板的重载成员函数和成员函数,如果您显式实例化run模板以绑定到特定的重载函数或函数模板实例化。

现场示例:http : //ideone.com/vsBS4H