2011-02-02 17 views
24

Quiero pasar un rvalue a través de std::bind a una función que toma una referencia rvalue en C++ 0x. No puedo entender cómo hacerlo. Por ejemplo:Pasar rvalues ​​a través de std :: bind

#include <utility> 
#include <functional> 

template<class Type> 
void foo(Type &&value) 
{ 
    Type new_object = std::forward<Type>(value); // move-construct if possible 
} 

class Movable 
{ 
public: 
    Movable(Movable &&) = default; 
    Movable &operator=(Movable &&) = default; 
}; 

int main() 
{ 
    auto f = std::bind(foo<Movable>, Movable()); 
    f(); // error, but want the same effect as foo(Movable()) 
} 

Respuesta

26

La razón de que esto no es así porque cuando se especifica foo<Movable>, la función que está uniéndose a es:

void foo(Movable&&) // *must* be an rvalue 
{ 
} 

Sin embargo, el valor pasado por std::bind no habrá un valor de lado derecho, pero un lvalue (almacenado como miembro en algún lugar del functor resultante bind). Eso, es el funtor generado es similar a:

struct your_bind 
{ 
    your_bind(Movable arg0) : 
    arg0(arg0) 
    {} 

    void operator()() 
    { 
     foo<int>(arg0); // lvalue! 
    } 

    Movable arg0; 
}; 

Construido como your_bind(Movable()). . Así se puede ver esto falla porque Movable&& No se puede enlazar a Movable

Una solución sencilla podría ser esto en su lugar:

auto f = std::bind(foo<Movable&>, Movable()); 

Porque ahora la función que está llamando es:

void foo(Movable& /* conceptually, this was Movable& && 
         and collapsed to Movable& */) 
{ 
} 

Y la llamada funciona bien (y, por supuesto, puede hacer ese foo<const Movable&> si lo desea). Sin embargo, una cuestión interesante es si podemos conseguir su vinculación original con el trabajo, y que puede a través de:

auto f = std::bind(foo<Movable>, 
      std::bind(static_cast<Movable&&(&)(Movable&)>(std::move<Movable&>), 
       Movable())); 

Es decir, que sólo std::move el argumento antes de hacer la llamada, por lo que se puede unir. Pero bueno, eso es feo. El molde es necesario porque std::move es una función sobrecargada, por lo que debemos especificar qué sobrecarga queremos mediante conversión al tipo deseado, eliminando las otras opciones.

En realidad no sería tan malo si std::move no estaba sobrecargado, como si tuviéramos algo como:

Movable&& my_special_move(Movable& x) 
{ 
    return std::move(x); 
} 


auto f = std::bind(foo<Movable>, std::bind(my_special_move, Movable())); 

que es mucho más simple. Pero a menos que tenga una función así, creo que está claro que probablemente solo quiera especificar un argumento de plantilla más explícito.


† Esto es diferente a llamar a la función sin argumentos plantilla explícita, ya que especifica explícitamente que elimina la posibilidad de que pueda ser deducida. (T&&, donde T es un parámetro de plantilla, se puede deducir nada a , if you let it be.)

+0

Gracias. De hecho, estoy tratando de hacer lo último (pasando el valor r a foo como valor r para habilitar la semántica de movimiento). Sin embargo, esperaba algo así como un 'std :: ref' para referencias de valores; el enlace de tu última solución es demasiado largo, y el enlace anidado te distrae. – Timothy003

+0

@Timothy: Bueno, tener un rvalue 'int' realmente no es tan útil, es solo un' int'. ¿Estás trabajando con un tipo más complejo? Si es así, tal vez debas editar tu pregunta para dejar en claro lo que estás esperando. (Si es el caso, creo que puede haber un malentendido sobre el uso canónico de la semántica de movimientos; no se preocupe, definitivamente no es muy conocido todavía) – GManNickG

+0

@GMan Pero no debería ser posible implementar std :: bind así, que usa std :: forward internamente para pasar rvalue si él mismo tiene rvalue? ¿No es eso un error en la implementación de std :: bind o en la especificación (sigue siendo borrador, por lo que tiene sentido notificar errores, ¿no?) –

0

(Esto es en realidad un comentario a la respuesta de GMan, pero necesito algo de formato para el código). Si funtor generada en realidad es así:

 
struct your_bind 
{ 
    your_bind(Movable arg0) : 
    arg0(arg0) 
    {} 

    void operator()() 
    { 
     foo(arg0); 
    } 

    Movable arg0; 
}; 

luego

 
int main() 
{ 
    auto f = your_bind(Movable()); 
    f(); // No errors! 
} 

compliles sin errores. ya que es posible asignar e inicializar datos con rvalue y luego pasar un valor de datos a rvaluear el argumento de foo().
Sin embargo, supongo que la implementación de enlace extrae el tipo de argumento de función directamente desde la firma foo(). yo.mi. el funtor generado es:

 
struct your_bind 
{ 
    your_bind(Movable && arg0) : 
    arg0(arg0) // **** Error:cannot convert from Movable to Movable && 
    {} 

    void operator()() 
    { 
     foo(arg0); 
    } 

    Movable&& arg0; 
}; 

y, de hecho, esto realmente no se puede inicializar el miembro de datos rvalue. Tal vez, la implpementación de enlace simplemente no extraiga correctamente el tipo "sin referencia" del tipo de argumento de función y use este tipo para la declaración de miembro de datos de functor "tal cual", sin recorte & &.

el funtor correcta debe ser:

 
struct your_bind 
{ 
    your_bind(Movable&& arg0) : 
    arg0(arg0) 
    {} 

    void operator()() 
    { 
     foo(arg0); 
    } 

    Movable arg0; // trim && !!! 
}; 


0

se puede utilizar una expresión lambda.

auto f = [](){ foo(Movable()); }; 

Esto parecería ser la opción más sencilla.

+0

Perdón, marqué su respuesta ... Heres why: Aunque esto funcionaría, bind generalmente se usa para recordar el contexto original ... La forma en que se usó en la pregunta original sería la misma que cierre de una [...] lambda ... En su lugar, la solución supone que PUEDE retrasar la creación de Movable hasta el momento en que esté listo para llamar a foo. ¿Qué tipo de pierde el punto de vinculación en primer lugar. Si puedes hacer eso, ¿por qué incluso entregar el objeto movible como un parámetro en primer lugar, por qué no simplemente construirlo en foo directamente? –

+0

Indica claramente en la pregunta original que quiere el efecto de 'foo (Movable())'. – Puppy

1

chicos con los que he hackeado una versión reenvío perfecto de un aglutinante (limitado a 1 param) aquí http://code-slim-jim.blogspot.jp/2012/11/stdbind-not-compatable-with-stdmove.html

de remisión, el código es

template <typename P> 
class MovableBinder1 
{ 
    typedef void (*F)(P&&); 

private: 
    F func_; 
    P p0_; 

public: 
    MovableBinder1(F func, P&& p) : 
    func_(func), 
    p0_(std::forward<P>(p)) 
    { 
    std::cout << "Moved" << p0_ << "\n"; 
    } 

    MovableBinder1(F func, P& p) : 
    func_(func), 
    p0_(p) 
    { 
    std::cout << "Copied" << p0_ << "\n"; 
    } 

    ~MovableBinder1() 
    { 
    std::cout << "~MovableBinder1\n"; 
    } 

    void operator()() 
    { 
    (*func_)(std::forward<P>(p0_)); 
    } 
}; 

Como u puede ver en la demostración anterior de concepto, es muy posible ...

veo ninguna razón por la std :: bind es incompatible con std :: movimiento ... std :: adelante es después de todo, para el reenvío perfecto no entiendo por qué no hay una enfermedad de transmisión sexual: : forwarding_bind ???

+1

Y aquí está la solución genérica http://code-slim-jim.blogspot.jp/2012/11/perfect-forwarding-bind-compatable-with.html –

0

Una mayor mejoría en la respuesta de GManNickG y tengo solución bastante:

auto f = std::bind(
    foo<Movable>, 
    std::bind(std::move<Movable&>, Movable()) 
); 

(funciona en gcc-4.9.2 y msvc2013)

Cuestiones relacionadas