Нужно суммировать поля из контейнера структур

Это скорее вопрос чистоты кода, потому что у меня уже есть пример. Я делаю это в тонне кода, и создание всех этих лямбда-выражений (некоторые из которых одинаковы) начало меня раздражать.

Итак, учитывая структуру:

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.


person Jonathan Mee    schedule 26.07.2017    source источник
comment
Я не вижу проблемы с предоставлением небольшой полезной функции. Иногда людям приходится искать информацию о вещах и в стандартной библиотеке. Мало кто знает все это наизусть. Пока она написана чисто и доступна, почему бы не добавить абстракцию поверх стандартной библиотеки?   -  person StoryTeller - Unslander Monica    schedule 26.07.2017
comment
@StoryTeller Прямо сейчас я тоже склоняюсь к этому. Этот вопрос - моя последняя остановка перед тем, как я это сделаю, просто кажется, что должен быть лучший способ.   -  person Jonathan Mee    schedule 26.07.2017
comment
Рассматривали ли вы метод класса: static void foo::acc4 (std::vector‹foo_t›foos) для выполнения всех 4 накоплений за один проход?   -  person 2785528    schedule 26.07.2017
comment
@DOUGLASO.MOEN У меня не было... это очень удобно для struct foo, но у меня на самом деле тонна больше участников, так что в моем случае это было бы расточительно. Однако огромное предложение, если вы хотите напечатать его, я определенно дам вам голос, хотя, вероятно, не приму, поскольку это не решает мою проблему.   -  person Jonathan Mee    schedule 26.07.2017
comment
@StoryTeller Только что нашел кое-что невероятное в C++14. Я думаю, что это лучше, чем функция: stackoverflow.com/a/45443542/2642059 Мысли?   -  person Jonathan Mee    schedule 01.08.2017


Ответы (2)


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

person eerorika    schedule 26.07.2017

На самом деле существует действительно элегантный способ решить эту проблему с помощью шаблонов переменных, которые были введены в 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)(); }
person Jonathan Mee    schedule 01.08.2017
comment
@StoryTeller Концепция, кажется, имеет смысл. Я даже начал обновлять свой ответ, но по какой-то причине у меня возникли проблемы с передачей адреса шаблонной функции на accumulate: ideone.com/ WTfxQR Я почти уверен, что это законно: stackoverflow.com/q/38402133/2642059 Что я здесь делаешь неправильно? - person Jonathan Mee; 01.08.2017
comment
Вы пропустили квалификатор const в указателе на элемент. Я удалил свой пост, потому что решение С++ 98 не так надежно, как общая лямбда (как показывает ваш пример, называя типы). - person StoryTeller - Unslander Monica; 01.08.2017
comment
@StoryTeller Спасибо, я знал, что что-то пропустил. C++17 позволяет нам пойти еще дальше, не давая мне совершать такие ошибки: template <auto T> auto func(const auto init, const auto i) { return init + (i->*T)(); } - person Jonathan Mee; 01.08.2017
comment
Да, язык становится удивительным по степени выразительности, которую он позволяет. Однако стоимость является чрезвычайно сложным стандартом. - person StoryTeller - Unslander Monica; 01.08.2017
comment
Я чувствую, что могу также поделиться тем, что я использовал функцию шаблона при работе с устаревшими API-интерфейсами C. Библиотеке требовалось много обратных вызовов. В конце концов я придумал простой шаблон, который позволил мне легко генерировать переадресацию обратных вызовов участникам. Я был очень горд собой :) В качестве дополнительного бонуса, это сделало отслеживание обратных вызовов очень простым. Все, что для этого потребовалось, — это одна точка останова в функции шаблона. - person StoryTeller - Unslander Monica; 01.08.2017