¿Por qué usar std:: bind sobre lambdas en C++14?


Antes de C++11 usé boost::bind o boost::lambda mucho. La parte bind se convirtió en la biblioteca estándar (std::bind) la otra parte se convirtió en parte del lenguaje central (C++ lambdas) e hizo el uso de lambdas mucho más fácil. Hoy en día, apenas uso std::bind, ya que puedo hacer casi cualquier cosa con C++ lambdas. Hay un caso de uso válido para std::bind que se me ocurre:

struct foo
{
  typedef void result_type;

  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"

El equivalente en C++14 para eso sería

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

Mucho más corto y más conciso. (En C++11 esto no funciona todavía debido a los parámetros automáticos.) ¿Hay algún otro caso de uso válido para std::bind superar la alternativa lambdas de C++ o es std::bind superfluo con C++14?

Author: Edgar Rokjān, 2013-06-28

6 answers

Scott Meyers dio una charla sobre esto. Esto es lo que recuerdo:

En C++14 no hay nada útil que bind pueda hacer que no se pueda hacer también con lambdas.

En C++11 sin embargo, hay algunas cosas que no se pueden hacer con lambdas:

  1. No puede mover las variables mientras captura al crear las lambdas. Las variables siempre se capturan como lvalues. Para bind puedes escribir:

    auto f1 = std::bind(f, 42, _1, std::move(v));
    
  2. Las expresiones no se pueden capturar, solo los identificadores pueden. Para bind puedes escribir:

    auto f1 = std::bind(f, 42, _1, a + b);
    
  3. Sobrecarga de argumentos para objetos de función. Esto ya se mencionó en la pregunta.

  4. Argumentos imposibles de perfeccionar

En C++14 todo esto es posible.

  1. Mover ejemplo:

    auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
    
  2. Ejemplo de expresión:

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

  4. Perfecto reenvío: Usted puede escribir

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

Algunas desventajas de bind:

  • Bind enlaza por nombre y como resultado si tiene varias funciones con el mismo nombre (funciones sobrecargadas) bind no sabe cuál usar. El siguiente ejemplo no compilará, mientras que lambdas no tendría ningún problema con él:

    void f(int); void f(char); auto f1 = std::bind(f, _1, 42);
    
  • Cuando se usan funciones bind es menos probable que estén en línea

Por otro lado, lambdas teóricamente podría generar más código de plantilla que unir. Ya que para cada lambda se obtiene un tipo único. Para bind es solo cuando tienes diferentes tipos de argumentos y una función diferente (supongo que en la práctica, sin embargo, no sucede muy a menudo que se enlaza varias veces con los mismos argumentos y función).

Lo que Jonathan Wakely mencionó en su respuesta es en realidad una razón más para no usar bind. No veo por qué querrías ignorar los argumentos en silencio.

 49
Author: BertR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-01 12:32:58

std::bind todavía puede hacer una cosa lambdas polimórfica no puede: invocar funciones sobrecargadas

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

El envoltorio de llamada creado por la expresión bind llama a diferentes funciones dependiendo de los argumentos que le dé, el cierre de un lambda polimórfico de C++14 puede tomar diferentes tipos de argumentos, pero no puede tomar un número diferente de argumentos, y siempre invoca (especializaciones de) la misma función en el cierre. Corrección: ver los comentarios abajo

El wrapper devuelto por std::bind también se puede llamar con demasiados argumentos y los ignorará, mientras que un cierre creado por una lambda diagnosticará los intentos de pasar demasiados argumentos ... pero no lo considero un beneficio de std::bind :)

 29
Author: Jonathan Wakely,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-07 15:51:28

Para mí, un uso válido para std::bind es dejar claro que estoy usando una función miembro como predicado. Es decir, si todo lo que hago es llamar a una función miembro, es bind. Si hago cosas adicionales con el argumento (además de llamar a una función memeber), es una 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
Author: utnapistim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-28 12:48:43

A veces es solo menos código. Considere esto:

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

Entonces

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

Vs lambda

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

Creo que bind es más fácil de leer aquí en comparación con la lambda que parece un https://en.wikipedia.org/wiki/Brainfuck

 1
Author: AlexTheo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-15 11:46:23

Otra diferencia es que los argumentos para enlazar deben ser copiados o movidos, mientras que una lambda puede usar variables capturadas por referencia. Véase el ejemplo siguiente:

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

No estoy seguro de si es posible solucionar el problema de usar el enlace anterior sin cambiar la firma de void p(const int&).

 0
Author: Zitrax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-30 14:06:03

Simplemente expandiendo el comentario de @BertR a esta respuesta a algo comprobable, aunque confieso que no pude conseguir una solución usando std::forward para trabajar.

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

Prueba en Compiler Explorer

 0
Author: Orwellophile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-10 16:09:59