web-dev-qa-db-fra.com

Pourquoi utiliser std :: bind sur lambdas en C++ 14?

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?

48
Ralph Tandetzky

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:

  1. 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));
    
  2. 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);
    
  3. Surcharge des arguments pour les objets fonction. Cela a déjà été mentionné dans la question.

  4. Impossible de perfectionner les arguments

En C++ 14 tout cela est possible.

  1. Déplacer exemple:

    auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
    
  2. Exemple d'expression:

    auto f1 = [sum = a + b](auto arg) { f(42, arg, sum); };
    
  3. Voir question

  4. 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);
    
  • Lorsque vous utilisez des fonctions de reliure, elles sont moins susceptibles d'être en ligne

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.

54
BertR

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 :)

29
Jonathan Wakely

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

1
AlexTheo

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);
1
utnapistim

É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

0
Orwellophile

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&).

0
Zitrax