Avant C++ 11, j’utilisais beaucoup boost::bind
ou boost::lambda
. La partie bind
a été intégrée à la bibliothèque standard (std::bind
), l’autre partie est devenue partie intégrante du langage de base (lambdas C++) et simplifie considérablement l’utilisation de lambdas. De nos jours, je n’utilise presque plus std::bind
, car je peux presque tout faire avec les lambdas C++. Il y a un cas d'utilisation valide pour std::bind
auquel je peux penser:
struct foo
{
template < typename A, typename B >
void operator()(A a, B b)
{
cout << a << ' ' << b;
}
};
auto f = bind(foo(), _1, _2);
f( "test", 1.2f ); // will print "test 1.2"
L’équivalent C++ 14 pour cela serait
auto f = []( auto a, auto b ){ cout << a << ' ' << b; }
f( "test", 1.2f ); // will print "test 1.2"
Beaucoup plus court et plus concis. (En C++ 11, cela ne fonctionne pas encore à cause des paramètres automatiques.) Existe-t-il un autre cas d'utilisation valide pour que std::bind
batte la variante lambdas C++ ou est-ce que std::bind
est superflu avec C++ 14?
Scott Meyers a donné une conversation à ce sujet. Voici ce dont je me souviens:
En C++ 14, il n'y a rien d'utile à faire avec bind qui ne puisse être aussi fait avec lambdas.
En C++ 11 cependant, certaines choses ne peuvent pas être faites avec lambdas:
Vous ne pouvez pas déplacer les variables lors de la capture lors de la création des lambdas. Les variables sont toujours capturées en tant que valeurs. Pour lier, vous pouvez écrire:
auto f1 = std::bind(f, 42, _1, std::move(v));
Les expressions ne peuvent pas être capturées, seuls les identificateurs le peuvent. Pour lier, vous pouvez écrire:
auto f1 = std::bind(f, 42, _1, a + b);
Surcharge des arguments pour les objets fonction. Cela a déjà été mentionné dans la question.
En C++ 14 tout cela est possible.
Déplacer exemple:
auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
Exemple d'expression:
auto f1 = [sum = a + b](auto arg) { f(42, arg, sum); };
Voir question
Transfert parfait: vous pouvez écrire
auto f1 = [=](auto&& arg) { f(42, std::forward<decltype(arg)>(arg)); };
Quelques inconvénients de bind:
La liaison est liée par nom et par conséquent, si vous avez plusieurs fonctions portant le même nom (fonctions surchargées), bind ne sait pas laquelle utiliser. L'exemple suivant ne compilera pas, tandis que lambdas n'aura pas de problème avec cela:
void f(int); void f(char); auto f1 = std::bind(f, _1, 42);
D'autre part, lambdas pourrait théoriquement générer plus de code de template que bind. Puisque pour chaque lambda, vous obtenez un type unique. Pour bind, ce n'est que lorsque vous avez différents types d'argument et une fonction différente (je suppose qu'en pratique cependant, cela n'arrive pas très souvent que vous liez plusieurs fois avec les mêmes arguments et fonction).
Ce que Jonathan Wakely a mentionné dans sa réponse est en réalité une raison de plus de ne pas utiliser bind. Je ne vois pas pourquoi vous voudriez ignorer les arguments en silence.
std::bind
peut toujours faire une chose que les lambda polymorphes ne peuvent pas: invoquer des fonctions surchargées
struct F {
bool operator()(char, int);
std::string operator()(char, char);
};
auto f = std::bind(F(), 'a', std::placeholders::_1);
bool b = f(1);
std::string s = f('b');
Le wrapper d'appel créé par l'expression de liaison appelle différentes fonctions en fonction des arguments que vous lui avez donnés. La fermeture d'un lambda polymorphe C++ 14 peut prendre différents types d'arguments, mais ne peut pas prendre un autre numéro des arguments, et invoque toujours (spécialisations) la même fonction à la fermeture. Correction: voir les commentaires ci-dessous
Le wrapper renvoyé par std::bind
peut également être appelé avec trop d'arguments et il les ignorera, alors qu'une fermeture créée par un lambda diagnostiquera les tentatives de passer trop d'arguments ... mais je ne considère pas cela comme un avantage. de std::bind
:)
Parfois, c'est juste moins de code. Considère ceci:
bool check(int arg1, int arg2, int arg3)
{
return ....;
}
Ensuite
wait(std::bind(check,a,b,c));
vs lambda
wait([&](){return check(a,b,c);});
Je pense que cette reliure est plus facile à lire ici comparée au lambda qui ressemble à un https://en.wikipedia.org/wiki/Brainfuck
Pour moi, une utilisation valide de std::bind
est de préciser que j’utilise une fonction membre comme prédicat. Autrement dit, si tout ce que je fais est d'appeler une fonction membre, c'est bind. Si je fais des choses supplémentaires avec l'argument (en plus d'appeler une fonction memeber), c'est un lambda:
using namespace std;
auto is_empty = bind(&string::empty, placeholders::_1); // bind = just map member
vector<string> strings;
auto first_empty = any_of(strings.begin(), strings.end(), is_empty);
auto print_non_empty = [](const string& s) { // lambda = more than member
if(s.empty()) // more than calling empty
std::cout << "[EMPTY]"; // more than calling empty
else // more than calling empty
std::cout << s; // more than calling empty
};
vector<string> strings;
for_each(strings.begin(), strings.end(), print_non_empty);
Étendre simplement le commentaire de @ BertR à cette réponse à quelque chose de vérifiable, bien que j'avoue que je ne pouvais pas vraiment obtenir une solution utilisant std :: forward <> pour fonctionner.
#include <string>
#include <functional>
using namespace std::string_literals;
struct F {
bool operator()(char c, int i) { return c == i; };
std::string operator()(char c, char d) { return ""s + d; };
};
void test() {
{ // using std::bind
auto f = std::bind(F(), 'a', std::placeholders::_1);
auto b = f(1);
auto s = f('b');
}
{ // using lambda with parameter pack
auto x = [](auto... args) { return F()('a', args...); };
auto b = x(1);
auto s = x('b');
}
}
Test sur Compiler Explorer
Une autre différence est que les arguments à lier doivent être copiés ou déplacés, alors qu'un lambda peut utiliser des variables capturées par référence. Voir exemple ci-dessous:
#include <iostream>
#include <memory>
void p(const int& i) {
std::cout << i << '\n';
}
int main()
{
std::unique_ptr<int> f = std::make_unique<int>(3);
// Direct
p(*f);
// Lambda ( ownership of f can stay in main )
auto lp = [&f](){p(*f);};
lp();
// Bind ( does not compile - the arguments to bind are copied or moved)
auto bp = std::bind(p, *f, std::placeholders::_1);
bp();
}
Pas sûr s'il est possible de contourner le problème pour utiliser bind ci-dessus sans changer la signature de void p(const int&)
.