Это больше вопрос кода, потому что у меня уже есть пример. Я делаю это в тонне кода, и создание всех этих лямбд (некоторые из которых являются одинаковыми) начинает раздражать меня.
Итак, учитывая структуру:
struct foo {
int b() const { return _b; }
int a() const { return _a; }
int r() const { return _r; }
const int _b;
const int _a;
const int _r;
};
У меня есть контейнер с указателями на них, скажем, vector<foo*> foos
Теперь я хочу пройти через контейнер и получить сумму одного из полей.
В качестве примера, если я хотел поле _r
тогда мой текущий подход заключается в том, чтобы сделать это:
accumulate(cbegin(foos), cend(foos), 0, [](const auto init, const auto i) { return init + i->r(); } )
Я пишу эту строку везде. Можно ли улучшить это? Я бы очень хотел написать что-то вроде этого:
x(cbegin(foos), cend(foos), mem_fn(&foo::r));
Я не думаю, что стандарт предусматривает что-то подобное. Я, конечно, мог бы написать это, но тогда читателю потребовалось бы выяснить мой подозрительный код, а не просто знать, что accumulate
делает.
Вместо написания пользовательского накопления я предлагаю написать собственный генератор функторов, который возвращает функтор, который можно использовать в качестве аргумента для std::accumulate
,
template<class Fun>
auto mem_accumulator(Fun member_function) {
return [=](auto init, auto i) {
return init + (i->*member_function)();
};
}
затем
accumulate(cbegin(foos), cend(foos), 0, mem_accumulator(&foo::r));
Несколько вариантов:
Для контейнеров предметов:
template<class MemFun>
auto mem_accumulator(MemFun member_function) {
return [=](auto init, auto i) {
return init + (i.*member_function)();
};
}
Используйте указатели на элементы данных вместо функций:
template<class T>
auto mem_accumulator(T member_ptr) {
return [=](auto init, auto i) {
return init + i->*member_ptr;
};
}
// ...
accumulator(&foo::_r)
Функторы поддержки, а не указатели на функции-члены:
template<class Fun>
auto accumulator(Fun fun) {
return [=](auto init, auto i) {
return init + fun(i);
};
}
// ...
accumulator(std::mem_fun(&foo::r))
Некоторые (все?) Из этих вариаций могут быть объединены для автоматического выбора с помощью некоторой магии SFINAE, но это увеличит сложность.
Существует действительно очень элегантный способ решить эту проблему, используя Шаблоны переменных которые были введены в C ++ 14. Мы можем шаблонизировать лямбда-переменную, используя указатель метода в качестве аргумента шаблона:
template <int (foo::*T)()>
auto func = [](const auto init, const auto i){ return init + (i->*T)(); };
Проходя func
соответствующая специализация func
в качестве последнего аргумента accumulate
будет иметь тот же эффект, что и запись лямбды на месте:
accumulate(cbegin(foos), cend(foos), 0, func<&foo::r>)
Еще одна альтернатива, основанная на той же предпосылке шаблонизации, которая не требует C ++ 14, это шаблонизированная функция предложено StoryTeller:
template <int (foo::*T)()>
int func(const int init, const foo* i) { return init + (i->*T)(); }
Что также можно использовать, просто передав указатель на метод:
accumulate(cbegin(foos), cend(foos), 0, &func<&foo::r>)
Специфичность, требуемая обоими этими примерами, была удалена в C ++ 17 где мы можем использовать auto
для типов параметров шаблона: http://en.cppreference.com/w/cpp/language/auto Это позволит нам объявить func
так что это может быть использовано любой класс, а не только foo
:
template <auto T>
auto func(const auto init, const auto i) { return init + (i->*T)(); }