2016-10-06 2 views
0

Допустим, у нас есть класс Obj и это главное:Выполнить функцию в качестве параметра в C++

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

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

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

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

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

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

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

Я не знаю, как заменить FUNCTION и PARAM, чтобы сделать эту работу.

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

+0

Да, это возможно. Попробуйте и вернитесь с проблемами, если столкнетесь с ними. Здесь у вас есть множество связанных вопросов. – Barry

+3

Ну, есть ['std :: function'] (http://en.cppreference.com/w/cpp/utility/functional/function) и [' std :: bind'] (http: //en.cppreference. ком/ж/CPP/утилиты/функционал/привязки). Или ['std :: mem_fn'] (http://en.cppreference.com/w/cpp/utility/functional/mem_fn). Как и простые старые указатели на функции-члены. –

+0

@JoachimPileborg Ваш комментарий - это ответ на самом деле, подумайте о его форматировании как ответе – alexeykuzmin0

ответ

5

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

// get the pointer to the function. 
auto funPtr = &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 (auto&& 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(&Obj::func1, 100); 

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

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

template<typename F, typename... Args> 
void invokeAll(F f, Args... args) { 
    for (auto&& 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 (auto&& 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 (auto&& obj : retrieveObjs()) { 
     f(obj, args...); 
    } 
} 

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

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

Затем, что вы разблокировать этот синтаксис тоже:

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

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

+0

Действительно, у меня возникнет соблазн просто взять лямбду и заставить вызывающего сделать клей, а не использовать указатель на функцию-член, в C++ 11 и 14. – Yakk

+1

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

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

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

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

Существует немного шаблонов для написания лямбда, но структура не станет вашей проблемой.


Я считаю, это иногда полезно, а также:

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

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

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

Если вы хотите разделить интерфейс от реализации, вы можете заменить class F и F&& на std::function<void(Obj&)> за скромную стоимость исполнения.