web-dev-qa-db-fra.com

Méthode correcte pour définir une fonction de prédicat en C ++

J'essaie d'écrire une fonction de prédicat à utiliser avec les algorithmes STL. Je vois qu'il y a deux façons de définir un prédicat:

(1) Utilisez une fonction simple comme ci-dessous:

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

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

(2) Utilisez la fonction operator () comme ci-dessous:

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

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

J'ai plus d'utilité pour le deuxième type car je voudrais généralement créer un objet prédicat avec certains membres et les utiliser dans l'algorithme. Lorsque j'ajoute la même fonction isEven dans le vérificateur et que je l'utilise comme prédicat, j'obtiens une erreur:
3. Syntaxe qui donne une erreur:

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

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

L'appel de c.isEven donne une erreur lors de la compilation indiquant une référence non définie à une fonction. Quelqu'un peut-il expliquer pourquoi 3. donne une erreur? Aussi, j'apprécierais tous les pointeurs à lire sur les bases des prédicats et des itérateurs.

23
cppcoder

Je suppose que c'est parce que le type de c.isEven() est,

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

ce qui n'est peut-être pas attendu par find_if(). std::find_if Devrait s'attendre à un pointeur de fonction (bool (*)(unsigned int)) ou à un objet fonction.

Edit: Une autre contrainte: Un non -staticle pointeur de fonction membre doit être appelé par l'objet class. Dans votre cas, même si vous réussissez à passer la fonction membre, find_if() n'aura toujours aucune information sur les objets checker; donc cela n'a pas de sens d'avoir find_if() surchargé pour accepter un argument de pointeur de fonction membre.

Note: En général, c.isEven N'est pas la bonne façon de passer un pointeur de fonction membre; il doit être passé comme, &checker::isEven.

5
iammilind

Un pointeur vers une fonction membre nécessite une instance pour être appelée, et vous ne passez que le pointeur de la fonction membre à std::find_if (En fait, votre syntaxe est incorrecte, donc elle ne fonctionne pas du tout; la syntaxe correcte est std::find_if(itBegin, itEnd, &checker::isEven) qui ne fonctionne toujours pas pour les raisons que j'ai données).

La fonction find_if S'attend à pouvoir appeler la fonction en utilisant un seul paramètre (l'objet à tester), mais il en faut en fait deux pour appeler une fonction membre: le pointeur d'instance this et l'objet comparer.

La surcharge de operator() vous permet de transmettre à la fois l'instance et l'objet fonction en même temps, car ils sont désormais la même chose. Avec un pointeur de fonction membre, vous devez transmettre deux informations à une fonction qui n'en attend qu'un.

Il existe un moyen de le faire en utilisant std::bind (Qui nécessite l'en-tête <functional>):

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

Si votre compilateur ne prend pas en charge std::bind, Vous pouvez également utiliser boost::bind Pour cela. Bien qu'il n'y ait aucun avantage réel à faire cela sur une simple surcharge de operator().


Pour élaborer un peu plus, std::find_if Attend un pointeur de fonction correspondant à la signature bool (*pred)(unsigned int) ou quelque chose qui se comporte de cette façon. Il n'est pas nécessaire qu'il s'agisse d'un pointeur de fonction, car le type du prédicat est lié par le modèle. Tout ce qui se comporte comme une bool (*pred)(unsigned int) est acceptable, c'est pourquoi les foncteurs fonctionnent: ils peuvent être appelés avec un seul paramètre et renvoyer un bool.

Comme d'autres l'ont souligné, le type de checker::isEven Est bool (checker::*pred)(unsigned int) qui ne se comporte pas comme le pointeur de fonction d'origine, car il a besoin d'une instance de checker pour être appelée .

Un pointeur vers une fonction membre peut être considéré conceptuellement comme un pointeur de fonction normal qui prend un argument supplémentaire, le pointeur this (par exemple bool (*pred)(checker*, unsigned int)). Vous pouvez en fait générer un wrapper qui peut être appelé de cette façon en utilisant std::mem_fn(&checker::isEven) (également à partir de <functional>). Cela ne vous aide toujours pas, car vous avez maintenant un objet fonction qui doit être appelé avec deux paramètres plutôt qu'un seul, ce que std::find_if N'aime toujours pas.

L'utilisation de std::bind Traite le pointeur vers une fonction membre comme s'il s'agissait d'une fonction prenant le pointeur this comme premier argument. Les arguments passés à std::bind Spécifient que le premier argument doit toujours être &c Et que le deuxième argument doit être lié au premier argument de l'objet fonction nouvellement retourné. Cet objet fonction est un wrapper qui peut être appelé avec un argument, et peut donc être utilisé avec std::find_if.

Bien que le type de retour de std::bind Ne soit pas spécifié, vous pouvez le convertir en std::function<bool(unsigned int)> (dans ce cas particulier) si vous avez besoin de faire référence à l'objet fonction lié explicitement plutôt que de le passer directement à une autre fonction comme je l'ai fait dans mon exemple.

12
Sven

checker::isEven N'est pas une fonction; c'est une fonction membre. Et vous ne pouvez pas appeler une fonction membre non statique sans référence à un objet checker. Vous ne pouvez donc pas simplement utiliser une fonction membre dans n'importe quel ancien endroit où vous pourriez passer un pointeur de fonction. Les pointeurs membres ont une syntaxe spéciale qui nécessite plus que simplement () Pour être appelés.

C'est pourquoi les foncteurs utilisent operator(); cela rend l'objet appelable sans avoir à utiliser un pointeur de fonction membre.

4
Nicol Bolas

Je préfère foncteurs (objets de fonction) parce que rendre votre programme plus lisible et, plus important encore, exprimer clairement l'intention.

Voici mon exemple préféré:

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

Remarque: Ces dernières années, nous avons un support expression lambda .

// Same example with lambda expression
double result = accumulate(cbegin(nums), cend(nums), 1.1,
                            [](double x, double y) { return x * y; });
2
Marko Tunjic

L'exemple donné indique que vous devez utiliser l'opérateur d'appel (operator()) alors que dans votre exemple vous avez appelé votre fonction isEven. Essayez de le réécrire comme:

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