web-dev-qa-db-fra.com

Comment passer et exécuter une fonction anonyme en tant que paramètre en C ++ 11?

Le code que je recherche est comme suit.

bool Func1(int Arg1, C++11LambdaFunc Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }
}

Plus tard, j'utiliserai ce code.

Func1(12, [](int D) -> bool { ... } );
33
Mohsen Sarkar

Version de base, à utiliser dans un fichier d'en-tête:

template<typename Lambda>
bool Func1(int Arg1, Lambda Arg2){ // or Lambda&&, which is usually better
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

Version plus complexe, si vous souhaitez séparer votre interface de votre implémentation (elle a des coûts de temps d'exécution):

bool Func1(int Arg1, std::function<bool(int)> Arg2){
  if(Arg1 > 0){
    return Arg2(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

std::function Utilise l'effacement de type pour créer un wrapper personnalisé autour de votre lambda, puis expose une interface non virtuelle qui utilise le modèle pImpl pour le transmettre au wrapper créé sur mesure.1

Ou, en termes moins techniques, std::function<bool(int)> est une classe qui peut encapsuler presque tout ce que vous pouvez appeler comme une fonction, en passant un paramètre compatible avec le passage d'un int, et elle renvoie quelque chose compatible avec le renvoi d'un bool.

Un appel via un std::function A un coût d'exécution à peu près égal à un appel de fonction virtual (provoqué par l'effacement du type ci-dessus), et lorsque vous le créez, il doit copier l'état de la fonction objet (alias functor) passé (qui peut être bon marché - lambdas sans état, ou lambdas capturant des arguments par référence - ou cher dans certains autres cas) et le stocker (généralement sur le magasin ou le tas gratuit, qui a un coût), tandis que les versions de modèle pur peuvent être "intégrées" au point d'appel (c'est-à-dire, non seulement coûter moins cher qu'un appel de fonction, le compilateur peut même optimiser sur l'appel de fonction et renvoyer les limites!)

Une version sophistiquée du premier exemple qui gère également certains cas d'angle un peu mieux: (doit également être implémentée dans un fichier d'en-tête ou dans la même unité de traduction que celle utilisée)

template<typename Lambda>
bool Func1(int Arg1, Lambda&& Arg2){
  if(Arg1 > 0){
    return std::forward<Lambda>(Arg2)(Arg1);
  } else {
    return false; // remember, all control paths must return a value
  }
}

qui utilise une technique connue sous le nom de "transfert parfait". Pour certains foncteurs, cela génère un comportement légèrement différent du n ° 1 (et généralement un comportement plus correct).

La plupart des améliorations proviennent de l'utilisation de && Dans la liste des arguments: cela signifie qu'une référence au foncteur est passée (au lieu d'une copie), ce qui permet d'économiser certains coûts et permet à la fois un const ou non - const foncteur à transmettre.

Le changement de std::forward<Lambda>(...) ne provoquerait un changement de comportement que si quelqu'un utilisait une fonctionnalité C++ relativement nouvelle qui permet aux méthodes (y compris operator()) de remplacer le statut rvalue/lvalue du this. En théorie, cela pourrait être utile, mais le nombre de foncteurs que j'ai vus qui remplacent réellement en fonction du statut rvalue de this est 0. Lorsque j'écris du code de bibliothèque sérieux (tm), je vais dans cette situation, mais rarement autrement.

Il y a encore une chose à considérer. Supposons que vous souhaitiez prendre soit une fonction qui renvoie bool, soit une fonction qui renvoie void, et si la fonction renvoie void, vous voulez la traiter comme si elle renvoyait true. Par exemple, vous prenez une fonction qui est appelée lors de l'itération sur une collection et vous souhaitez éventuellement prendre en charge l'arrêt précoce. La fonction renvoie false lorsqu'elle veut s'arrêter prématurément, et true ou void sinon.

Ou, dans un cas plus général, si vous avez plusieurs remplacements d'une fonction, dont l'un prend une fonction et d'autres prennent un autre type au même emplacement.

C'est possible, pour autant que je vais entrer ici (soit avec un adaptateur intelligent, soit via les techniques SFINAE). Cependant, il vaut probablement mieux créer deux fonctions nommées différentes, car les techniques requises sont beaucoup trop lourdes.


1 Techniquement, std::function Pourrait utiliser de la poussière de fée magique pour faire ce qu'il fait, car son comportement est décrit par la norme, et non son implémentation. Je décris une implémentation simple qui se rapproche du comportement de l'implémentation std::function Avec laquelle j'ai interagi.

45

Première solution:

Vous pouvez faire de votre fonction Func1() une fonction modèle:

template<typename T>
bool Func1(int Arg1, T&& Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false; // <== DO NOT FORGET A return STATEMENT IN A VALUE-RETURNING
                  //     FUNCTION, OR YOU WILL GET UNDEFINED BEHAVIOR IF FLOWING
                  //     OFF THE END OF THE FUNCTION WITHOUT RETURNING ANYTHING
}

Vous pouvez alors l'invoquer à votre guise:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}

Deuxième solution:

Si vous ne souhaitez pas utiliser de modèles, une alternative (qui entraînerait une surcharge d'exécution) consiste à utiliser std::function :

#include <functional>

bool Func1(int Arg1, std::function<bool(int)> Arg2){
    if(Arg1 > 0){
        return Arg2(Arg1);
    }

    return false;
}

Encore une fois, cela vous permettrait d'appeler Func1() comme vous le souhaitez:

int main()
{
    Func1(12, [](int D) -> bool { return D < 0; } );
}
19
Andy Prowl

Pour ceux dont les goûts sont plus traditionnels, notez que les lambdas non capturants peuvent être convertis en pointeurs fonctionnels. Vous pouvez donc écrire votre fonction ci-dessus comme:

bool Func1(int Arg1, bool (*Arg2)(int)) { ... }

Et cela fonctionnera correctement pour les deux fonctions traditionnelles et lambdas.

11
Andy Ross