Ответ 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...*/);
Я предпочитаю 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);
Я предоставляю полную примерную программу ниже.
Теперь вы редактировали вопрос с новой информацией о том, что вы пытаетесь сделать.
Я уверен, что вы не хотите этого делать, поскольку прагмы OpenMP/OpenACC, такие как parallel for
, обычно требуют дополнительных комментариев для обеспечения разумной производительности, и они зависят от того, что вы точно пытаетесь сделать при вызове сайт.
Тем не менее, если вы действительно хотите пойти по этому маршруту, вы можете написать свой собственный алгоритм for_each
и отправить в соответствии с ExecutionAgent
(см. N3874 и N3731). Если параллельная задача OpenMP, TBB, OpenACC выполняется слишком медленно, вы также можете легко обеспечить перегрузки на основе, например, a 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 в лучшем случае будет посредственной. Вы можете взглянуть на параллельную реализацию libstdС++, где они используют 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);
}