#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;)>
небольшими затратами на производительность.