2010-04-29 24 views
13

Esta es una pregunta de seguimiento al my previous question.Expresiones de enlace anidado

#include <functional> 

int foo(void) {return 2;} 

class bar { 
public: 
    int operator() (void) {return 3;}; 
    int something(int a) {return a;}; 
}; 

template <class C> auto func(C&& c) -> decltype(c()) { return c(); } 

template <class C> int doit(C&& c) { return c();} 

template <class C> void func_wrapper(C&& c) { func(std::bind(doit<C>, std::forward<C>(c))); } 

int main(int argc, char* argv[]) 
{ 
    // call with a function pointer 
    func(foo); 
    func_wrapper(foo); // error 

    // call with a member function 
    bar b; 
    func(b); 
    func_wrapper(b); 

    // call with a bind expression 
    func(std::bind(&bar::something, b, 42)); 
    func_wrapper(std::bind(&bar::something, b, 42)); // error 

    // call with a lambda expression 
    func([](void)->int {return 42;}); 
    func_wrapper([](void)->int {return 42;}); 

    return 0; 
} 

estoy recibiendo un error de compilación de profundidad en las cabeceras de C++:

functional:1137: error: invalid initialization of reference of type ‘int (&)()’ from expression of type ‘int (*)()’
functional:1137: error: conversion from ‘int’ to non-scalar type ‘std::_Bind<std::_Mem_fn<int (bar::*)(int)>(bar, int)>’ requested

func_wrapper (foo) se supone que debe ejecutar func (doit (foo)) En el código real, empaqueta la función para que se ejecute un hilo. func sería la función ejecutada por el otro subproceso, doit se encuentra en el medio para comprobar excepciones no controladas y para limpiar. Pero el enlace adicional en func_wrapper ensucia las cosas ...

+0

tal vez eliminar C++ tag?esto es directo C++ 0x – Anycorn

+10

Mantener ambas etiquetas. C++ 0x es C++ también. – jalf

+0

Dele a PC-Lint un giro (desde www.gimpel.com). Es bastante bueno para dar mensajes de error mucho mejores y detallados que Visual Studio. Sin embargo, es bastante caro. –

Respuesta

2

Al ver esto por segunda vez ahora, y creo que tengo una explicación plausible para el primer error que está viendo.

En este caso, es más útil observar el error completo y las instancias de plantilla que conducen a él. El error impreso por mi compilador (GCC 4.4), por ejemplo, termina con las siguientes líneas:

test.cpp:12: instantiated from ‘decltype (c()) func(C&&) [with C = std::_Bind<int (*(int (*)()))(int (&)())>]’ 
test.cpp:16: instantiated from ‘void func_wrapper(C&&) [with C = int (&)()]’ 
test.cpp:22: instantiated from here 
/usr/include/c++/4.4/tr1_impl/functional:1137: error: invalid initialization of reference of type ‘int (&)()’ from expression of type ‘int (*)()’ 

Mira ahora en esta abajo hacia arriba, el mensaje de error real parece correcta; los tipos que el compilador ha deducido son incompatibles.

La primera instanciación de plantilla, en func_wrapper, muestra claramente qué tipo de compilador ha deducido del parámetro real foo en func_wrapper(foo). Personalmente esperaba que fuera un puntero a la función, pero de hecho es una función referencia.

La segunda instancia de plantilla es difícil de leer. Pero jugar un poco con std::bind un poco, me enteré de que el formato de las impresiones del CCG representación textual de un funtor se unen es aproximadamente:

std::_Bind<RETURN-TYPE (*(BOUND-VALUE-TYPES))(TARGET-PARAMETER-TYPES)> 

Así destrozarlo:

std::_Bind<int (*(int (*)()))(int (&)())> 
// Return type: int 
// Bound value types: int (*)() 
// Target parameter types: int (&)() 

Aquí es donde el incompatibles tipos comienzan Aparentemente, aunque c en func_wrapper es una referencia de función, se convierte en una función puntero una vez pasado a std::bind, lo que resulta en el tipo de incompatibilidad. Por lo que vale, std::forward no importa en absoluto en este caso.

Mi razonamiento aquí es que std::bind solo parece importarle los valores, y no las referencias. En C/C++, no hay tal cosa como un valor de función; solo hay referencias y punteros. Entonces, cuando se desreferencia la referencia de la función, el compilador solo puede darle un puntero a la función de manera significativa.

El único control que tiene sobre esto son sus parámetros de plantilla. Deberá decirle al compilador que está tratando con un puntero a la función desde el inicio para que esto funcione. Probablemente es lo que tenías en mente de todos modos.Para hacer eso, especificar explícitamente el tipo que desee para el parámetro de plantilla C:

func_wrapper<int (*)()>(foo); 

O la solución más breve, tenga explícitamente la dirección de la función:

func_wrapper(&foo); // with C = int (*)() 

Me pondré en contacto con usted si Alguna vez descubrí el segundo error. :)

1

Al principio, por favor, permítanme presentarles a 2 puntos clave:

  • un: Cuando se utiliza anidado std :: bind, el std :: bind interior se evalúa primero, y el regreso el valor se sustituirá en su lugar mientras se evalúa el std :: bind externo. Eso significa que std::bind(f, std::bind(g, _1))(x) se ejecuta como lo hace f(g(x)). El std :: bind interno se supone que está envuelto por std :: ref si el std :: bind externo quiere un functor en lugar de un valor de retorno.

  • b: La referencia del valor r no se puede reenviar correctamente a la función utilizando std :: bind. Y el reason ya se ha ilustrado en detalle.

Entonces, veamos la pregunta. La función más importante aquí podría ser func_wrapper que se pretende realizar 3 propósitos:

  1. Perfect reenviar un funtor a doIT plantilla de función en un primer momento,
  2. a continuación, utilizando std :: unen para hacer doit a modo de cierre,
  3. y dejar que la plantilla de función func ejecute el functor devuelto por std :: bind al fin.

Según el punto b, el objetivo 1 no se puede cumplir. Entonces, olvidemos el reenvío perfecto y la plantilla de función doit tiene que aceptar un parámetro de referencia de valor l.

Según el punto a, el objetivo 2 se realizará mediante el uso de std :: ref.

Como resultado, la versión final podría ser:

#include <functional> 

int foo(void) {return 2;} 

class bar { 
public: 
    int operator() (void) {return 3;}; 
    int something(int a) {return a;}; 
}; 

template <class C> auto func(C&& c) -> decltype(c()) { return c(); } 

template <class C> int doit(C&/*&*/ c) // r-value reference can't be forwarded via std::bind 
{ 
    return c(); 
} 

template <class C> void func_wrapper(C&& c) 
{ 
    func(std::bind(doit<C>, 
        /* std::forward<C>(c) */ // forget pefect forwarding while using std::bind 
        std::ref(c)) // try to pass the functor itsself instead of its return value 
     ); 
} 

int main(int argc, char* argv[]) 
{ 
    // call with a function pointer 
    func(foo); 
    func_wrapper(foo); // error disappears 

    // call with a member function 
    bar b; 
    func(b); 
    func_wrapper(b); 

    // call with a bind expression 
    func(std::bind(&bar::something, b, 42)); 
    func_wrapper(std::bind(&bar::something, b, 42)); // error disappears 

    // call with a lambda expression 
    func([](void)->int {return 42;}); 
    func_wrapper([](void)->int {return 42;}); 

    return 0; 
} 

Pero, si realmente quiere lograr el propósito 1 y 2, ¿cómo? Prueba esto:

Voy a explicar algunos puntos:

  • Para reducir una gran cantidad de sentencias de retorno, el cambio de la firma funtor de int() para anular la().
  • Las 2 plantillas de funciones run() se utilizan para comprobar si el parámetro del functor original es perfecto reenviado o no.
  • dispatcher_traits va a mapear la constante bool para escribir.
  • Será mejor que nombre dispatcher :: forward para diferir de dispatcher :: dispatch o debe invocar la plantilla std :: bind con la firma dispatcher :: forward.