webentwicklung-frage-antwort-db.com.de

Warum sollte man std :: bind über Lambdas in C++ 14 verwenden?

Vor C++ 11 habe ich viel boost::bind oder boost::lambda verwendet. Der bind-Teil hat es in die Standardbibliothek (std::bind) geschafft, der andere Teil wurde Teil der Kernsprache (C++ - Lambdas) und machte die Verwendung von Lambdas wesentlich einfacher. Heutzutage verwende ich kaum std::bind, da ich fast alles mit C++ - Lambdas machen kann. Es gibt einen gültigen Anwendungsfall für std::bind, den ich mir vorstellen kann:

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"

Das C++ 14-Äquivalent dafür wäre 

auto f = []( auto a, auto b ){ cout << a << ' ' << b; }
f( "test", 1.2f ); // will print "test 1.2"

Viel kürzer und prägnanter. (In C++ 11 funktioniert dies aufgrund der Auto-Parameter noch nicht.) Gibt es einen anderen gültigen Anwendungsfall für std::bind, der die C++ - Lambdas-Alternative übertrifft, oder ist std::bind mit C++ 14 überflüssig?

48
Ralph Tandetzky

Scott Meyers gab ein talk darüber. Daran erinnere ich mich:

In C++ 14 gibt es nichts sinnvolles bind, das nicht auch mit Lambdas gemacht werden kann.

In C++ 11 gibt es jedoch einige Dinge, die mit Lambdas nicht möglich sind:

  1. Sie können die Variablen während der Erfassung beim Erstellen der Lambdas nicht verschieben. Variablen werden immer als l-Werte erfasst. Für bind kannst du schreiben:

    auto f1 = std::bind(f, 42, _1, std::move(v));
    
  2. Ausdrücke können nicht erfasst werden, nur Bezeichner. Für bind kannst du schreiben:

    auto f1 = std::bind(f, 42, _1, a + b);
    
  3. Argumente für Funktionsobjekte überladen. Dies wurde bereits in der Frage erwähnt.

  4. Es ist unmöglich, Argumente zu perfektionieren

In C++ 14 sind all diese Möglichkeiten möglich.

  1. Beispiel verschieben:

    auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
    
  2. Ausdruck Beispiel:

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

  4. Perfekte Weiterleitung: Sie können schreiben

    auto f1 = [=](auto&& arg) { f(42, std::forward<decltype(arg)>(arg)); };
    

Einige Nachteile von bind:

  • Bind bind bindet nach Name und als Ergebnis, wenn Sie mehrere Funktionen mit demselben Namen haben (überladene Funktionen), weiß bind nicht, welche verwendet werden soll. Das folgende Beispiel wird nicht kompiliert, während Lambdas damit kein Problem haben würden:

    void f(int); void f(char); auto f1 = std::bind(f, _1, 42);
    
  • Bei Verwendung von Bindefunktionen ist die Wahrscheinlichkeit geringer, dass Inline-Funktionen verwendet werden

Auf der anderen Seite könnten Lambdas theoretisch mehr Vorlagencode generieren als binden. Denn für jedes Lambda erhalten Sie einen eindeutigen Typ. Für bind gilt dies nur, wenn Sie verschiedene Argumenttypen und eine andere Funktion haben (ich schätze, dass es in der Praxis jedoch nicht oft vorkommt, dass Sie mehrere Male mit denselben Argumenten und Funktionen binden).

Was Jonathan Wakely in seiner Antwort erwähnte, ist eigentlich ein Grund mehr, Bind nicht zu benutzen. Ich kann nicht verstehen, warum Sie Argumente ignorieren möchten.

54
BertR

std::bind kann immer noch eine Sache, die polymorphe Lambdas nicht können: überladene Funktionen aufrufen

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

Der Aufruf-Wrapper, der vom Bind-Ausdruck erstellt wird, ruft abhängig von den von Ihnen angegebenen Argumenten verschiedene Funktionen auf. Die Schließung eines polymorphen C++ 14-Lambda kann verschiedene types der Argumente annehmen, jedoch keine andere number von Argumenten und ruft immer die gleiche Funktion auf die Schließung auf. Korrektur: siehe die Kommentare unten

Der von std::bind zurückgegebene Wrapper kann auch mit too many -Argumenten aufgerufen werden und wird sie ignorieren, wohingegen ein von einem Lambda erstellter Abschluß Versuche diagnostiziert, zu viele Argumente zu übergeben ... aber ich halte das nicht für einen Vorteil von std::bind :)

29
Jonathan Wakely

Manchmal ist es nur weniger Code. Bedenken Sie:

bool check(int arg1, int arg2, int arg3)
{
  return ....;
}

Dann

wait(std::bind(check,a,b,c));

gegen Lambda

wait([&](){return check(a,b,c);});

Ich denke, dass bind hier leichter zu lesen ist, verglichen mit dem Lambda, das wie ein https://en.wikipedia.org/wiki/Brainfuck aussieht.

1
AlexTheo

Für mich ist eine gültige Verwendung für std::bind, um klar zu machen, dass ich eine Member-Funktion als Prädikat verwende. Das heißt, wenn ich nur eine Member-Funktion nenne, ist das bind. Wenn ich zusätzliches Zeug mit dem Argument mache (außer dem Aufrufen einer Memeber-Funktion), ist es ein 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

Erweitere einfach den Kommentar von @ BertR auf diese Antwort auf etwas Testbares, obwohl ich gestehe, dass ich mit std :: forward <> keine Lösung finden konnte, um zu funktionieren.

#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 am Compiler Explorer

0
Orwellophile

Ein weiterer Unterschied besteht darin, dass zu bindende Argumente kopiert oder verschoben werden müssen, während ein Lambda Variablen verwenden kann, die durch Verweis erfasst werden. Siehe Beispiel unten:

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

Nicht sicher, ob es möglich ist, das oben beschriebene Problem zu umgehen, ohne die Signatur von void p(const int&) zu ändern.

0
Zitrax