Подтвердить что ты не робот

Правильный способ определения функции предиката в С++

Я пытаюсь написать предикатную функцию для использования с алгоритмами 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 внутри checker и использую ее как предикат, я получаю сообщение об ошибке:
3. Синтаксис, который дает ошибку:

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

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

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

4b9b3361

Ответ 1

Я предполагаю, что тип 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.

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

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; });