Выполнение функции в качестве параметра в C

#c #templates #c 11 #lambda

#c #шаблоны #c 11 #лямбда

Вопрос:

Допустим, у нас есть класс Obj и этот основной:

 class Obj
{
   public:
     void func1(int n) {}
     void func2(std:string n) {}
};

std::vector<Obj> retrieveObjs()
{
    std::vector<Obj> resu<
    // ...
    return resu<
}

int main()
{
    // Call func1 for all obj
    {
      auto objs = retrieveObjs();  
      for (autoamp; obj : objs)       
      {
         obj.func1(100);
      }
    }

    // Call func2 for all obj
    {
      auto objs = retrieveObjs();  
      for (autoamp; obj : objs)       
      {
         obj.func2("xxx");
      }
    }
    return 0;
}
  

Я хотел бы иметь общую функцию для вызова определенной функции из всех объектов, например, следующего псевдокода.

 void invokeAll(FUNCTION f, PARAM p)   // pseudocode
{
   auto objs = retrieveObjs();
   for (autoamp; obj : objs)       
   {
     obj.f(p); 
   }
}

int main() // pseudocode
{
    invokeAll(func1, 100);
    invokeAll(func2, "xxx");
}
  

Я не знаю, как заменить FUNCTION и PARAM заставить это работать.

Возможно ли это с помощью template / lambda/ for_each или аналогичных трюков для этого?

Комментарии:

1. Да, это возможно. Попробуйте и вернитесь с проблемами, если столкнетесь с какими-либо. В вашем распоряжении множество связанных с этим вопросов.

2. Ну, есть std::function и std::bind . Или std::mem_fn . А также простые старые указатели на функции-члены.

3. @JoachimPileborg Ваш комментарий на самом деле является ответом, рассмотрите возможность форматирования его как ответа

Ответ №1:

То, что вы описываете, является хорошим вариантом использования для указателя на функцию-член, синтаксис которой выглядит следующим образом:

 // get the pointer to the function.
auto funPtr = amp;Obj::func1;

Obj obj;

// call the method using the function pointer
obj.(*funPtr)();
  

В вашем случае вы можете получить указатель на функцию в качестве параметра и аргументы в виде пакета.

 // F is the type of the function pointer.
// As arguments and return type of `f` can change, so it's type `F` can.
template<typename F, typename... Args>
void invokeAll(F f, Args... args) {
    for (autoamp;amp; obj : retrieveObjs()) {
      // We call member `f` with `obj`
      // We expand the pack `args` to send it as multiple arguments
      obj.(*f)(args...); 
    }
}
  

Вы сможете вызывать функцию аналогичным образом, как вы хотели:

 // Notice the member function pointer syntax
invokeAll(amp;Obj::func1, 100);

// Work with multiple arguments, [100, "test"] will be packed into `args`
invokeAll(amp;Obj::func2, 100, "test");
  

В C 17, с std::invoke , вы можете еще больше обобщить свой случай, разрешив любой тип функции, которая принимает a Obj в качестве параметра:

 template<typename F, typename... Args>
void invokeAll(F f, Args... args) {
    for (autoamp;amp; obj : retrieveObjs()) {
      // invoke function `f` with `obj` as it's object and `args` as parameter.
      std::invoke(f, obj, args...); 
    }
}
  

Если вы хотите прямо сейчас поддерживать больше функций, включая лямбды, вы можете использовать void_t стиль sfinae:

 // The compiler will pick this function if `obj.(*f)(args...)` can compile
template<typename F, typename... Args>
auto invokeAll(F f, Args... args) -> void_t<decltype(std::declval<Obj>().(*f)(args...))> {
    //                   Here's the constraint ------^
    for (autoamp;amp; obj : retrieveObjs()) {
      obj.(*f)(args...); 
    }
}

// The compiler will pick this function if `f(obj, args...)` can compile
template<typename F, typename... Args>
auto invokeAll(F f, Args... args) -> void_t<decltype(f(std::declval<Obj>(), args...))> {
    //                   Here's the constraint ------^
    for (autoamp;amp; obj : retrieveObjs()) {
      f(obj, args...); 
    }
}
  

void_t определяется следующим образом:

 template<typename...>
using void_t = void;
  

Затем, с помощью этого, вы также разблокируете этот синтаксис:

 invokeAll([](Objamp; obj, int a){
    // this block will be called for each `obj` in `retrieveObjs`
}, 100);
  

Если вы также хотите поддерживать не копируемые типы, ищите идеальную пересылку.

Комментарии:

1. На самом деле, у меня возникло бы искушение просто взять лямбда-выражение и заставить вызывающего выполнять склеивание, а не использовать указатель на функцию-член, в C 11 и 14.

2. Я должен признать, что я тоже предпочитаю не использовать указатели на функции-члены. Лямбда — это блаженство. Однако указатели на функции-члены являются наиболее близкими к желаемому OP.

Ответ №2:

 template<class F, class R>
void invoke_on_range( Famp;amp; f, Ramp;amp; r ) {
  std::for_each( r.begin(), r.end(), std::forward<F>(f) );
}
  

это принимает диапазон и вызывает лямбда-выражение для каждого элемента диапазона.

 int main() {
  invoke_on_range( [](Objamp; obj){ obj.func1(100); }, retrieveObjs() );
  invoke_on_range( [](Objamp; obj){ obj.func2("xxx"); }, retrieveObjs() );
}
  

Для написания лямбда-выражения есть немного шаблонов, но структура становится не вашей проблемой.


Я тоже иногда нахожу это полезным:

 template<class F, class...Args>
void invoke_on_each( Famp;amp; f, Argsamp;amp;...args ) {
  using discard=int[];
  (void)discard{0,(void(
    f( std::forward<Args>(args) )
  ),0)...};
}
  

Для этого требуется лямбда f -выражение и набор args... . Он выполняется f один раз для каждого args... . Странный discard трюк заключается в создании массива из всех 0 s и выбрасывании его (чего оптимизатор не будет делать), чтобы сгенерировать контекст, в котором ... будет делать именно то, что мы хотим.

Кажется, что скрывать тот факт, с которым вы работаете retrieveObjs , не стоит писать другую функцию переноса, но это тоже можно сделать.

Если вы хотите отделить интерфейс от реализации, вы можете заменить class F и Famp;amp; на с std::function<void(Objamp;)> небольшими затратами на производительность.