Как создать переменную функцию шаблона с помощью функции `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_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 также не является вариантом, поскольку циклы критичны для производительности, и я не могу иметь абстрактный вызов функции в каждой итерации цикла.

Ответ 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);
}

Ответ 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