简体   繁体   English

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

[英]How to create a variadic template function with `std::function` as a function parameter?

How can I create a variadic template function with std::function as a function parameter that accepts a variadic number of arguments? 如何使用std::function作为函数参数创建一个可变参数模板函数,该参数接受可变数量的参数? I tried to reduce the problem to a MWE: 我试图将问题减少到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
}

It seems like the mere presence of the variadic template parameter in run_variadic makes it impossible to directly call it with a member function pointer. 似乎run_variadic仅存在variadic模板参数使得无法使用成员函数指针直接调用它。 clang 's error message is as follows: 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.

Any suggestions on how I can fix run_variadic so that I do not have to go through the extra std::function object? 有关如何修复run_variadic任何建议,以便我不必通过额外的std::function对象?

Background 背景

I have a class hierarchy as 我有一个类层次结构

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

There are multiple Derived classes that all have to implement one or more methods to loop over a range of elements. 有多个Derived类都必须实现一个或多个方法来遍历一系列元素。 The loop looks something like 循环看起来像

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

All loops should be OpenMP-accelerated. 所有循环都应该是OpenMP加速的。 I want the accelerator stuff factored out and not repeated in each method of Derived that uses a loop, so that I only have to change one place if eg OpenMP would switch to OpenACC. 我希望将加速器内容考虑在内,而不是在使用循环的Derived每个方法中重复,因此我只需要更改一个位置,例如OpenMP将切换到OpenACC。

Thus I am looking for a way to put the loop (and its decoration) in its own function. 因此,我正在寻找一种方法将循环(及其装饰)放在自己的功能中。 Moving it to the Abstract base class is not an option either, since the loops are performance-critical and I cannot have an abstract function call in each loop iteration. 将它移动到Abstract基类也不是一个选项,因为循环是性能关键的,我不能在每个循环迭代中有一个抽象函数调用。

You are almost always certainly better off abstracting away the function object: 抽象掉函数对象几乎总是更好:

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

This allows you to do the right thing at call site: 这允许您在呼叫站点做正确的事情:

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

I prefer a lambda to std::function or std::bind but you can also use those if they are already available: 我更喜欢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);

I provide a full example program below. 我在下面提供完整的示例程序。

You have now edited the question with new information regarding what you are trying to do. 您现在已经使用有关您要执行的操作的新信息编辑了问题。

I'm pretty sure that you don't want to do this, since the OpenMP/OpenACC pragmas like parallel for usually require extra annotations for delivering reasonable performance, and they depend on what you are exactly trying to do at call site. 我很确定你不想这样做,因为像parallel for的OpenMP / OpenACC pragma通常需要额外的注释才能提供合理的性能,而且它们取决于你在呼叫站点上正在尝试做什么。

Still, if you really really want to go this route you can write your own for_each algorithm and dispatch according to an ExecutionAgent (see N3874 and N3731 ). 尽管如此,如果你真的想要走这条路线,你可以编写自己的 for_each算法并根据ExecutionAgent调度(参见N3874N3731 )。 If OpenMP, TBB, OpenACC parallel task are too slow, you can also easily provide overloads based on eg an ExecutionPolicy like this: 如果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));
}

And then you can implement overloads of for_each_ for each execution policy, eg: 然后,您可以为每个执行策略实现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

Note that the ExecutionPolicy is just a tag, ie: 请注意, 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

This will at least give you an efficient TBB backend even tho the OpenMP/OpenACC performance will be mediocre at best. 至少会为您提供高效的TBB后端,即使OpenMP / OpenACC性能最佳也是平庸的。 You can take a look at the parallel implementation of libstdc++ where they use OpenMP. 您可以查看libstdc ++的并行实现,它们使用OpenMP。 Their for_each algorithm is over 1000 lines of code and uses work-stealing. 他们的for_each算法超过1000行代码并使用工作窃取。

Full example program: 完整示例程序:

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

To answer your comment on the previous answer, that answer can be adapted to support pointers to member functions in the way that you've asked for. 要回答您对上一个答案的评论,可以调整该答案,以支持您所要求的方式指向成员函数的指针。 The previous answer already works for all callable objects, but not directly with a pointer to member function because those are not callable with the usual f(args) syntax. 前面的答案已经适用于所有可调用对象,但不能直接使用指向成员函数的指针,因为它们不能使用通常的f(args)语法调用。 The following version uses tag dispatch to distinguish between pointers to member functions and traditional callable objects, applying the call syntax appropriate to each case. 以下版本使用标记分派来区分指向成员函数的指针和传统的可调用对象,并应用适合于每种情况的调用语法。

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

This can be used in all the same ways as the previous answer could, but also supports directly passing in a pointer to member function: 这可以以与前一个答案相同的方式使用,但也支持直接传入指向成员函数的指针:

run(&Foo::bar, foo);

It even works with overloaded member functions and member functions which are templates, if you explicitly instantiate the run template to bind to a particular overloaded function or function template instantiation. 如果您显式实例化run模板以绑定到特定的重载函数或函数模板实例,它甚至可以使用重载的成员函数和作为模板的成员函数。

Live example: http://ideone.com/vsBS4H 实例: http//ideone.com/vsBS4H

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM