Правильный способ определения функции предиката в C ++ - PullRequest
22 голосов
/ 28 июля 2011

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

(1) Используйте простую функцию, как показано ниже:

bool isEven(unsigned int i)   
{ return (i%2 == 0); }

std::find_if(itBegin, itEnd, isEven); 

(2) Используйте функцию operator (), как показано ниже:

class checker {  
public:  
  bool operator()(unsigned int i)  
  { return (i%2 == 0); }  
}; 

std::find_if(itBegin, itEnd, checker); 

Я больше использую второй тип, поскольку я обычно хотел бы создать объект предиката с некоторыми членами в нем и использовать их в алгоритме.Когда я добавляю ту же функцию isEven внутри средства проверки и использую ее как предикат, я получаю ошибку:
3. Синтаксис, который выдает ошибку:

class checker { 
    public: 
       bool isEven(unsigned int i) 
       { return (i%2 == 0); }
}; 

checker c; 
std::find_if(itBegin, itEnd, c.isEven); 

Вызов c.isEven выдает ошибку во время компиляции, говорянеопределенная ссылка на некоторую функцию.Может кто-нибудь объяснить, почему 3. дает ошибку?Кроме того, я был бы признателен за любые советы по прочтению основ предикатов и итераторов.

Ответы [ 5 ]

11 голосов
/ 28 июля 2011

Указатель на функцию-член требует вызова экземпляра, и вы передаете только указатель на функцию-член на std::find_if (на самом деле ваш синтаксис неправильный, поэтому он вообще не работает; правильный синтаксис std::find_if(itBegin, itEnd, &checker::isEven), который по-прежнему не работает по причинам, которые я привел).

Функция find_if ожидает, что она сможет вызывать функцию с использованием одного параметра (объекта для тестирования), но для вызова функции-члена фактически требуется два: указатель экземпляра this и объект для сравнения.

Перегрузка operator() позволяет вам одновременно передавать экземпляр и объект функции, потому что теперь они - одно и то же. С указателем на функцию-член вы должны передать две части информации функции, которая ожидает только одну.

Есть способ сделать это, используя std::bind (для этого требуется заголовок <functional>):

checker c;
std::find_if(itBegin, itEnd, std::bind(&checker::isEven, &c, std::placeholders::_1));

Если ваш компилятор не поддерживает std::bind, вы также можете использовать boost::bind для этого. Хотя в этом нет особого преимущества перед перегрузкой operator().

<ч />

Чтобы уточнить немного, std::find_if ожидает указатель функции, соответствующий сигнатуре bool (*pred)(unsigned int) или что-то подобное. На самом деле это не обязательно должен быть указатель на функцию, потому что тип предиката связан шаблоном. Все, что ведет себя как bool (*pred)(unsigned int), приемлемо, поэтому функторы работают: их можно вызывать с помощью одного параметра и возвращать bool.

Как уже отмечали другие, тип checker::isEven равен bool (checker::*pred)(unsigned int), который не ведет себя как исходный указатель на функцию, потому что для его вызова требуется экземпляр checker.

Указатель на функцию-член концептуально может рассматриваться как обычный указатель на функцию, который принимает дополнительный аргумент, указатель this (например, bool (*pred)(checker*, unsigned int)). На самом деле вы можете создать оболочку, которая может быть названа таким образом, используя std::mem_fn(&checker::isEven) (также из <functional>). Это все равно не поможет, потому что теперь у вас есть функциональный объект, который должен вызываться с двумя параметрами, а не только с одним, что std::find_if по-прежнему не нравится.

Использование std::bind обрабатывает указатель на функцию-член так, как если бы она была функцией, принимающей указатель this в качестве первого аргумента. Аргументы, передаваемые std::bind, указывают, что первый аргумент всегда должен быть &c, а второй аргумент должен связываться с первым аргументом вновь возвращаемого функционального объекта. Этот функциональный объект является оберткой, которую можно вызывать с одним аргументом и, следовательно, можно использовать с std::find_if.

Хотя тип возвращаемого значения std::bind не указан, вы можете преобразовать его в std::function<bool(unsigned int)> (в данном конкретном случае), если вам нужно явно обратиться к объекту связанной функции, а не передавать его прямо другой функции, такой как I сделал в моем примере.

4 голосов
/ 28 июля 2011

Полагаю, это потому, что тип c.isEven() равен

bool (checker::*)(unsigned int) // member function of class

, чего нельзя ожидать от find_if().std::find_if должен ожидать указатель функции (bool (*)(unsigned int)) или функциональный объект.

Редактировать : Другое ограничение: указатель на функцию-член, не являющийся static , долженбыть вызванным class объектом .В вашем случае, даже если вам удастся передать функцию-член, все равно find_if() не будет иметь никакой информации о каком-либо объекте checker;поэтому не имеет смысла перегружать find_if() для принятия аргумента указателя на функцию-член.

Примечание : В общем случае c.isEven не является правильным способом передачи указателя на функцию-член;это должно быть передано как &checker::isEven.

4 голосов
/ 28 июля 2011

checker::isEven не является функцией;это функция-член.И вы не можете вызывать нестатическую функцию-член без ссылки на checker объект.Таким образом, вы не можете просто использовать функцию-член в любом старом месте, где вы можете передать указатель на функцию.Указатели-члены имеют специальный синтаксис, который требует больше, чем просто () для вызова.

Именно поэтому функторы используют operator();это делает объект вызываемым без необходимости использовать указатель на функцию-член.

2 голосов
/ 11 января 2016

Я предпочитаю функторы (функциональные объекты), потому что делают вашу программу более читабельной и, что более важно, четко выражают намерение.

Это мой любимый пример:

template <typename N>
struct multiplies
{
  N operator() (const N& x, const N& y) { return x * y; }
};

vector<int> nums{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Example accumulate with transparent operator functor 
double result = accumulate(cbegin(nums), cend(nums), 1.1, multiplies<>());

Примечание. В последние годы мы получили поддержку лямбда-выражения .

// Same example with lambda expression
double result = accumulate(cbegin(nums), cend(nums), 1.1,
                            [](double x, double y) { return x * y; });
1 голос
/ 28 июля 2011

В приведенном примере говорится, что вы должны использовать оператор вызова (operator()), тогда как в вашем примере вы вызвали свою функцию isEven. Попробуйте переписать это как:

class checker { 
    public: 
       bool operator()(unsigned int i) 
       { return (i%2 == 0); }
};
...