2012-03-30 6 views
7

Estoy tratando de entender el concepto de currying y llamar a una función que concatena tres cadenas pero pasando solo dos cadenas y usando el segundo argumento dos veces.C++ Función enlazar argumentos de repetición para curried function

Sin embargo, cuando hago esto, el segundo argumento no se envía a la función e imprime una cadena vacía. ¿Es un error realmente obvio?

string concatthreestrings(string a,string b,string c){ 
    cout<<"Value of A: "<<a<<endl; 
    cout<<"Value of B: "<<b<<endl; 
    cout<<"Value of C: "<<c<<endl; 
    return a+b+c; 
} 


int main() 
{ 
    typedef std::function< string(string,string) > fun_t ; 
    using namespace std::placeholders; 
    fun_t fn = std::bind(concatthreestrings, _1, _2, _2); 
    cout<<endl<<fn("First","Second")<<endl; 

} 

Esto está dando la salida siguiente. No usar el _2 dos veces significa que el segundo argumento se pasa tanto para el segundo como para el tercero. Si utiliza una cadena en su lugar, funciona bien.

enter image description here

+0

Pregunta muy interesante, ¡no esperaba tal comportamiento! –

+1

Solo para agregar algunas correcciones: Esto no es realmente currying. Los argumentos vinculantes y el currying son dos operaciones muy similares pero distintas, que no deben confundirse. Currying significa tomar una función que toma una función de N argumentos y convertirla en una función de un argumento que devuelve una función de un argumento que devuelve una función de un argumento ... (repetido n veces). Puede usar 'std :: bind' para implementar una función' curry' que hace esto por usted (hasta cierto punto). De forma similar, puede usar currying para implementar el enlace de argumentos en el modo de 'std :: bind'. – LiKao

+1

@LiKao: De hecho, 'bind' permite [aplicación parcial] (http://en.wikipedia.org/wiki/Partial_application), no currying. –

Respuesta

5

cadenas de copia es caro. Como std::bind cree que los valores de los marcadores de posición solo se usan una vez, realiza un std::move en las cadenas. Esto se hace para cada parámetro y, como consecuencia, ya sea b o c se mueve, es decir, cadena vacía.

Usted puede cambiar ese comportamiento diciendo explícitamente lo que quiere decir, haciendo pasar los argumentos por referencia const-:

string concatthreestrings(string const& a,string const& b,string const& c) 

Ahora, que debería funcionar.

+2

¿Está seguro de que este comportamiento está bien definido? Según lo que leí en el estándar, el contenedor devuelto por 'bind' reenvía sus argumentos a la función envuelta. En este caso, de acuerdo con mi entendimiento, tendríamos un contenedor de esta manera (después de deducir los parámetros de la plantilla y contraerlo): 'string g (const char (& u1) [6], const char (& u2) [7]) { concatthreestrings (reenviar (u1), reenviar (u2), reenviar (u2)); } ' Las tres cadenas 'a',' b' y 'c' se construirían a partir de las matrices, por lo que no veo dónde ocurriría el movimiento. –

+0

@ipc: ¿Hay alguna forma en que pueda especificar este comportamiento en el estándar std :: bind en lugar de cambiar la definición de la función en sí – ganeshran

+0

El acceso al objeto movido es UB. –

2

me hicieron unas pruebas utilizando este ejemplo más pequeño que presenta el mismo comportamiento que tenga:

#include <functional> 
#include <iostream> 
#include <string> 

using std::string; 

void print(string s1, string s2) 
{ 
    std::cout << s1 << s2 << '\n'; 
} 

int main() 
{ 
    using namespace std::placeholders; 

    typedef std::function< void(string) > fn_t; 

    fn_t func = std::bind(print, _1, _1); 

    std::string foo("foo"); 
    func(foo); 
} 

// outputs: foo 

Tenga en cuenta que he definido un objeto de cadena llamado "foo" en lugar de utilizar literales de cadena. El comportamiento es el mismo, por lo que el problema no está relacionado con esto.

Creo que el problema viene de su typedef. El retorno de bind (que no se especifica) se convierte en una función tomando stringpor valor, mientras que el envoltorio devuelto por bind probablemente toma sus argumentos por rvalue-reference y los reenvía perfectamente. En lugar de usar su propio typedef, debe usar la palabra clave auto, de modo que el compilador deduzca automáticamente el tipo de func. Si modificamos el principal de la siguiente manera, obtenemos el comportamiento esperado:

int main() 
{ 
    using namespace std::placeholders; 

    auto func = std::bind(print, _1, _1); 

    std::string foo("foo"); 
    func(foo); 
} 

// outputs: foofoo 

Otra solución es reemplazar el typedef para que func toma su parámetro por referencia a const:

typedef std::function< void(string const &) > fn_t; 

Pongo 'Realmente entiendo por qué el otro typedef no funciona ... Es de suponer que la cadena se mueve, como @ipc notó, pero no sé en qué punto de la ejecución sucede esto. Ni siquiera estoy seguro de que esto sea un comportamiento estándar, ya que tanto function como el contenedor devuelto por bind deben usar el reenvío perfecto. Quizás GCC incluye algunas optimizaciones que mueven los argumentos del contenedor cuando se pasan por valor?

Edición

Hice algunas pruebas, resulta que la aplicación de std::function de GCC realiza un movimiento en sus argumentos, mientras que el retorno envoltorio por std::bind no lo hace. Todavía no sé si esto es estándar, voy a escribir una pregunta sobre eso.

+0

Creo que es un problema de implementación del compilador ya que VC no lo exhibe. – ganeshran

Cuestiones relacionadas