【问题标题】:How to create a variadic template function with `std::function` as a function parameter?如何使用 `std::function` 作为函数参数创建可变参数模板函数?
【发布时间】:2014-05-22 20:06:48
【问题描述】:

如何创建一个可变参数模板函数,将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 基类也不是一种选择,因为循环对性能至关重要,而且我不能在每次循环迭代中调用抽象函数。

【问题讨论】:

  • 这只是一个 MWE,在实际代码中,run 方法包含一个并行循环,并在每个循环迭代中调用 fun
  • 您的问题似乎是编译器在尝试将成员函数指针与std::function 映射时不知道Args... 是什么,因为尚未推断出Args...(它在更右边)。
  • 不要这样使用std::function。它不起作用,即使它起作用,你也不会得到什么。
  • 此外,所提供的参数不太可能与function 参数的参数完全匹配。您可能会有额外的限定词和转换等等。
  • @MooingDuck 但即使你修复了它,例如使用std::decay_t&lt;T&gt; 或一些unqualified_t&lt;T&gt; trait,你仍然会遇到麻烦。

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


【解决方案1】:

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

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...*/);

比起std::functionstd::bind,我更喜欢lambda,但如果它们已经可用,你也可以使用它们:

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 调度(参见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);
}

【讨论】:

  • 我认为 lambda 语法是 C++14?
  • 另外,有没有办法让这种方法更简洁?我将不得不在不同的课程中多次调用run,并且我不想每次都重复相同的lambda模式(或std::bind等)。
  • @MichaelSchlottke 使用std::mem_fn 创建函数对象。如果您需要类型擦除,您也可以将其存储在 std::function 一次并在任何地方使用。如果您不需要类型擦除,您可能可以编写函数对象而不是普通函数。如果您需要将普通函数包装为函数对象... lambda 尽其所能,特别是对于成员函数,因为它们需要以某种方式存储 this 指针,并使用特殊语法(.*-&gt;*)。如果没有更多关于你到底想要达到什么目标的细节,很难给你更好的建议。
  • @MichaelSchlottke 回答您的第一条评论:这是您的可变参数函数的可变参数 C++14 lambda(我假设您需要绑定示例中的可变参数函数)。您可以使用 std::bind 在 C++11 中模拟它,它返回一个可以接受任意数量参数的多态函数对象,例如您将可变成员函数绑定到对象。
  • 我已经更新了我的问题,以提供有关我想要实现的目标的更多详细信息。
【解决方案2】:

要回答您对上一个答案的评论,该答案可以调整为以您要求的方式支持指向成员函数的指针。前面的答案已经适用于所有可调用对象,但不能直接使用指向成员函数的指针,因为它们不能使用通常的 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 模板以绑定到特定的重载函数或函数模板实例化,它甚至可以与作为模板的重载成员函数和成员函数一起使用。

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

【讨论】:

  • 赞成,因为这是 other 替代方案。这里的缺点是,如果run 做了一些不平凡的事情,您最终可能会在run_helper 中复制功能。如果没有关于 OP 试图实现的目标的更多信息,很难推荐一种替代方案:/
  • 在 C++17 中,您可以使用 std::invoke,它适用于所有可调用对象,包括指向成员的指针。
猜你喜欢
  • 1970-01-01
  • 2017-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-25
  • 2014-01-03
相关资源
最近更新 更多