如何使用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
基类也不是一个选项,因为循环是性能关键的,我不能在每次循环迭代中都有一个抽象函数调用。
答案 0 :(得分:14)
你几乎总是更好地抽象掉函数对象:
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::function
或std::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编译器通常需要额外的注释来提供合理的性能,而且它们取决于你正在尝试做什么在呼叫现场。
但是,如果您真的想要走这条路线,您可以编写自己的 for_each
算法并根据ExecutionAgent
发送(见N3874和N3731)。如果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性能最好也是平庸的。您可以查看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);
}
答案 1 :(得分:3)
要回答您对上一个答案的评论,可以调整该答案,以便按照您要求的方式支持指向成员函数的指针。前面的答案已经适用于所有可调用对象,但不能直接使用指向成员函数的指针,因为它们不能使用通常的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(&Foo::bar, foo);
如果您显式实例化run
模板以绑定到特定的重载函数或函数模板实例,它甚至可以使用重载的成员函数和成员函数作为模板。