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

Antes de C ++ 11 usaba boost::bind o boost::lambda mucho. La parte de bind convirtió en la biblioteca estándar ( std::bind ), la otra parte se convirtió en parte del lenguaje central (C ++ lambdas) y facilitó el uso de lambdas. Hoy en día, casi no uso std::bind , ya que puedo hacer casi cualquier cosa con C ++ lambdas. Hay un caso de uso válido para std::bind que puedo pensar:

 struct foo { typedef void result_type; template  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 C ++ 14 equivalente 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 aún no funciona debido a los parámetros automáticos). ¿Hay algún otro caso de uso válido para std::bind superando a C ++ lambdas alternative o std::bind superfluous with C ++ 14?

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

En C ++ 14 no hay nada útil que bind pueda hacer, que tampoco se puede hacer 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 fue mencionado en la pregunta.

  4. Imposible perfeccionar los argumentos

En C ++ 14 todos estos son posibles.

  1. Ejemplo de movimiento:

     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. Reenvío perfecto: puedes escribir

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

Algunas desventajas de bind:

  • Bind se une por su nombre y, como resultado, si tiene varias funciones con el mismo nombre (funciones sobrecargadas), bind no sabe cuál usar. El siguiente ejemplo no se comstackrá, mientras que lambdas no tendría un problema con él:

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

Por otro lado, las lambdas teóricamente podrían generar más código de plantilla que bind. Como para cada lambda obtienes 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 vincule varias veces con los mismos argumentos y funciones).

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

std::bind aún puede hacer una cosa que las lambdas polimórficas no pueden: 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 contenedor de llamadas creado por la expresión de enlace llama a diferentes funciones dependiendo de los argumentos que le dé, el cierre de un lambda polimórfico 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: vea los comentarios a continuación

El contenedor devuelto por std::bind también se puede llamar con demasiados argumentos y los ignorará, mientras que un cierre creado por un lambda diagnosticará los bashs de pasar demasiados argumentos … pero no considero que sea un beneficio de std::bind 🙂

Para mí, un uso válido para std::bind es dejar en 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 extra con el argumento (además de llamar a una función de memeber), es un lambda:

 using namespace std; auto is_empty = bind(&string::empty, placeholders::_1); // bind = just map member vector 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 strings; for_each(strings.begin(), strings.end(), print_non_empty); 

Otra diferencia es que los argumentos a vincular se deben copiar o mover, mientras que un lambda puede usar variables capturadas por referencia. Vea el siguiente ejemplo:

 #include  #include  void p(const int& i) { std::cout < < i << '\n'; } int main() { std::unique_ptr f = std::make_unique(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 resolver el problema para usar el enlace anterior sin cambiar la firma de void p(const int&) .

A veces es solo menos código. Considera 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 el lambda que se parece a https://en.wikipedia.org/wiki/Brainfuck

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

 #include  #include  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 el comstackdor Explorer