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

У меня есть std::vector объектов указателей Person, которые имеют функцию-член std::string getName() const. Используя алгоритмы STL, я хочу подсчитать все объекты Person в векторе, где getName() возвращает «Чад».

Поведение, просто повторяющее цикл, будет таким:

int num_chads = 0;
for(std::vector<Person *>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    if((*it)->getName() == "Chad")
        ++num_chads;
}

Я хочу переработать его, чтобы он использовал все алгоритмы STL, функторы и т. д. (сделать его более функционально-ориентированным). Вот что я думаю, что мне нужно сделать:

const int num_chads = std::count_if(vec.begin(), vec.end(),
                                    std::bind1st(std::bind2nd(std::equal_to, mem_fun(Person::getName)), "Chad"));

Как вы, наверное, понимаете, это не работает. Во-первых, насколько я понимаю, вы не можете использовать bind1st/bind2nd для объектов binder1st/binder2nd, так как они специально предназначены для работы с std::binary_functions. Во-вторых, и это гораздо важнее, я не думаю, что использую правильную технику. Я действительно хочу привязать один из аргументов к "Chad", но с аргументом итератора я на самом деле просто хочу преобразовать значение итератора в строку перед вызовом связанной версии equals_to.

Я думаю, что это можно сделать с помощью Boost, но возможно ли это, используя только ядро ​​​​C++ 03 (т.е. без C++ 0x lambas!)?

РЕДАКТИРОВАТЬ: Может ли кто-нибудь привести пример, в котором не используется определяемый пользователем предикат (т.е. просто используются инструменты, предоставленные в наборе инструментов std)?

РЕДАКТИРОВАТЬ: В то время как ответ Матье является ответом из учебника о том, как использовать функторы в алгоритмах STL, ответ Кубби исходил из подхода, который я искал (хотя Матье ответил до того, как я отредактировал вопрос, чтобы сделать его более конкретным, поэтому приношу извинения!).


person Christopher Howlin    schedule 16.03.2011    source источник
comment
Не ответ: мне нравятся функциональные приемы, но я отказался от stl-алгоритмов для объектов. Лямбда действительно является недостающим звеном в C++03. Даже с boost.lambda есть много причуд. Stl-алгоритмы хорошо работают с примитивами, но я редко использую контейнеры примитивов... Стоит ли избегать циклов при их замене на bind-конструкции?   -  person stefaanv    schedule 16.03.2011
comment
Нормально ли, что getName() вернется по копии?   -  person Matthieu M.    schedule 16.03.2011
comment
Boost::bind намного чище, чем std::bind, и вы можете комбинировать лямбда-выражения.   -  person CashCow    schedule 16.03.2011
comment
@stefaanv - я думаю, что с практической точки зрения в большинстве случаев я бы согласился. Если бы появился такой простой пример, я бы, вероятно, просто написал цикл. @Matthieu - я понимаю, откуда вы, но пример придуман для целей этого вопроса, и возврат по копии против возврата по const ref - это не то, о чем я действительно думал (я не думаю, что это меняет структуру вопрос).   -  person Christopher Howlin    schedule 16.03.2011
comment
@Rodion Ingles: вовсе нет, насколько я знаю, создание временного файла немного усложнит работу компилятора, но не должно повлиять на правильность представленных решений.   -  person Matthieu M.    schedule 16.03.2011
comment
Я пытаюсь (и не могу) найти решение того, о чем вы просите. Я надеюсь, что кто-то добился большего успеха, чем я, или что кто-то может объяснить, почему это невозможно сделать.   -  person Captain Giraffe    schedule 16.03.2011
comment
@Captain Giraffe, вы не могли этого сделать, потому что С++ 98 не включал требуемую часть STL, см. stackoverflow.com/questions/5325122/   -  person Cubbi    schedule 16.03.2011


Ответы (3)


Поскольку никто еще не опубликовал фактический код повышения, С++ 98 с повышением:

ptrdiff_t num_chads = std::count_if(vec.begin(), vec.end(),
                      boost::bind(&Person::getName, _1) == "Chad");

тестовый запуск https://ideone.com/PaVJe

Что касается чистого C++, я не думаю, что это возможно без адаптера compose1, присутствующего в STL, но не в C++ stdlib...

и вот он (используя реализацию STL в GCC)

ptrdiff_t num_chads = std::count_if(vec.begin(), vec.end(),
                     __gnu_cxx::compose1(
                         std::bind2nd(std::equal_to<std::string>(), "Chad"),
                         std::mem_fun(&Person::getName)));

тестовый запуск: https://ideone.com/EqBS5

РЕДАКТИРОВАТЬ: исправлено для учета Person*

person Cubbi    schedule 16.03.2011
comment
Небольшая разница, ОП использует std::vector<Person*>, откуда возникают все проблемы. Это означает, что вам нужно (очевидно) разыменовать Person* перед применением функции-члена, и есть вероятность, что она равна нулю (в общем случае). - person Matthieu M.; 16.03.2011
comment
@Matthieu M done: s/mem_fun_ref/mem_fun/m, хотя возможность разыменования нулевого указателя, безусловно, будет присутствовать в большинстве однострочных решений. То есть об этом можно позаботиться, но решение станет нечитаемым. - person Cubbi; 16.03.2011
comment
@Matthieu - имеет ли значение тот факт, что вектор содержит Person * (кроме упомянутой выше проблемы с нулевым указателем)? Единственное изменение, которое я вижу, это то, что вам нужно использовать mem_fun вместо mem_fun_ref, что фактически сводится к разнице между .* и -›* - person Christopher Howlin; 16.03.2011
comment
@Rodion: Надеюсь, что нет :P Я думаю, что boost::bind автоматически справляется с этим. - person Matthieu M.; 16.03.2011

Я всегда находил лямбды относительно нечитаемыми. Я предпочитаю писать явные типы:

struct Named
{
  Named(char const* ref): _ref(ref) {}
  bool operator()(Person* p) const { return p && p->getName() == _ref; }
  char const* _ref;
};

size_t const c = std::count_if(vec.begin(), vec.end(), Named("Chad"));

Хотя определение Named «не соответствует действительности», правильно выбранное имя передает намерение и скрывает детали реализации. Лично я считаю это хорошей вещью, потому что тогда я не отвлекаюсь на детали реализации и не пытаюсь выяснить, что происходит, реконструируя код (как бы очевидно это ни было).

person Matthieu M.    schedule 16.03.2011
comment
Это хорошее решение, но лично я вижу функторы, используемые для этого как лямбда бедняка, в основном из-за задействованного кода шаблона. - person stefaanv; 16.03.2011
comment
@stefaanv: я согласен с шаблоном, но поскольку мы используем имена для передачи намерений, анонимным функциям, конечно, не хватает такого уровня детализации, для таких простых функций можно утверждать, что это очевидно ... однако загадочный синтаксис std::bind* скрывает простота. - person Matthieu M.; 16.03.2011
comment
Да, это хорошее решение для балансировки более функционального подхода без каррирования функторов STL. Однако я действительно искал что-то, что не требовало написания ваших собственных объектов функций (я знаю, что это не было указано в вопросе). Тем не менее, это явно хороший пример использования пользовательского предиката в count_if. - person Christopher Howlin; 16.03.2011
comment
Но можно ли Named объявить в той же функции, в которой он используется? Каждый раз, когда я пытаюсь, я получаю ошибки области видимости. - person Robᵩ; 16.03.2011
comment
Я думаю, что size_t const c = std::count_if(vec.begin(), vec.end(), [](const Person& p){ return p.GetName() == Chad; }); все еще хорошо, но, вероятно, это быстро усложняется. - person stefaanv; 16.03.2011
comment
@Rob Adams: к сожалению, нет, я предлагаю анонимные пространства имен. Стандарт С++ 03 фактически запрещает использование локально определенных функций типов в качестве параметров шаблона (я думаю, что это проблема компоновки, но для подтверждения вам понадобится кто-то более савантный, чем я). - person Matthieu M.; 16.03.2011
comment
@stefaanv: Истинные лямбда-выражения вполне читаемы, я согласен, однако ОП указал, что ему нужно решение на С++ 03, и, к сожалению, я не могу придумать никакого элегантного решения с bind (в зависимости от версии). Мне бы очень хотелось, чтобы DeadMG расширил свой ответ, чтобы увидеть синтаксис, который он имел в виду. - person Matthieu M.; 16.03.2011
comment
@Rob - в этом ответе для краткости отсутствует некоторая информация. Named — это структура, что означает, что она может быть определена только в другом определении класса или как отдельная структура (это относится и к классам). Вы не можете определить класс внутри функции, что всегда было одной из самых больших проблем при использовании пользовательских функторов: их нужно определять вне точки, в которой они вызываются. Именно здесь появляются ламбы и делают вещи более привлекательными, позволяя вам эффективно определять одну функцию в другой. - person Christopher Howlin; 16.03.2011
comment
Подход правильный, но реализация нет. Внутри функтора вы сохраняете указатель на литерал и сравниваете этот указатель с результатом вызова функции getName(), который, скорее всего, не будет указателем на тот же строковый литерал. - person David Rodríguez - dribeas; 16.03.2011
comment
@David: нет, на самом деле, согласно моему замечанию, он возвращает std::string (по копии). - person Matthieu M.; 16.03.2011

Используйте boost::bind, он во многом превосходит существующие стандартные механизмы привязки. boost::bind полностью совместим с C++03.

person Puppy    schedule 16.03.2011
comment
Можете ли вы придумать способ сделать это без Boost? - person Christopher Howlin; 16.03.2011
comment
Или, если нет, дать ответ Boost? (Или оба! :P) - person Christopher Howlin; 16.03.2011
comment
@Rodion Ingles: Конечно, но это потребует от вас повторного написания boost::bind, что довольно бессмысленно. Что касается предоставления такого ответа, то документации вполне достаточно, чтобы вы могли сделать это самостоятельно. Задача ответчика помочь вам, а не держать вас за руку. - person Puppy; 16.03.2011
comment
Что ж, возможно, у вас есть смысл предоставить пример Boost, но ваш банальный ответ о написании boost::bind снова не служит никому, кроме вас. - person Christopher Howlin; 16.03.2011
comment
@Rodion Ingles: Это совсем не так. boost::bind как это делается. Либо вы используете boost::bind, либо вы выбрасываете свое собственное boost::bind, но в конце концов это все равно boost::bind под любым именем, которое вы ему дали. - person Puppy; 21.03.2011