Pasar rvalues a través de std:: bind


Quiero pasar un rvalue a través de std::bind a una función que toma una referencia rvalue en C++0x. Por ejemplo:

#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())
}
Author: 0x499602D2, 2011-02-02

5 answers

La razón por la que esto falla es porque cuando se especifica foo<Movable>, la función a la que se está vinculando es:

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

Sin embargo, el valor pasado por std::bind no será un rvalue, sino un lvalue (almacenado como un miembro en algún lugar del funtor bind resultante). Que, 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í que puedes ver que esto falla porque Movable&& no puede enlazar a Movable.†

Una solución simple podría ser la siguiente:

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

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

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

Y la llamada funciona bien (y, por supuesto, podrías hacer eso foo<const Movable&> si lo deseas). Pero una pregunta interesante es si podemos conseguir que su enlace original funcione, y podemos a través de:

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

Es decir, solo std::move el argumento antes de hacer la llamada, por lo que puede unirse. Pero vaya, eso es feo. El cast es necesario porque std::move es una función sobrecargada, por lo que tenemos que especificar qué sobrecarga queremos fundiendo al tipo deseado, eliminando las otras opciones.

En realidad no sería tan malo si std::move no estuviera sobrecargado, como si tuviéramos algo como: {[19]]}

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 por ahí, creo que está claro que probablemente solo desee especificar un argumento de plantilla más explícito.


† Esto es diferente a llamar a la función sin un argumento de plantilla explícito, porque especificarlo explícitamente elimina la posibilidad de que se deduzca. (T&&, donde T es un parámetro de plantilla, se puede deducir a cualquier cosa, si lo dejas ser .)

 27
Author: GManNickG,
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-05-23 12:33:38

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

Para referencia 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 prueba de concepto anterior, es muy posible...

No veo ninguna razón por la que std::bind sea incompatible con std::move... std:: forward es después de todo para el reenvío perfecto No entiendo por qué no hay un std:: forwarding_bind ???

 1
Author: Ashley Smart,
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
2012-11-11 13:08:13

(Esto es en realidad un comentario a la respuesta de GMan, pero necesito un poco de formato para el código). Si el funtor generado realmente 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!
}

Se completa sin errores. como es posible asignar e inicializar datos con rvalue y luego pasar un valor de datos al argumento rvalue de foo ().
Sin embargo, supongo que la implementación de bind extrae el tipo de argumento de función directamente de la firma foo (). es decir, el funtor generado es:

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

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

    Movable&& arg0;
};

Y de hecho, esto realmente falla al inicializar el miembro de datos rvalue. Tal vez, la implementación de bind simplemente no extrae correctamente el tipo "no referenciado" del tipo de argumento de función y usa este tipo para la declaración del miembro de datos del funtor "tal cual", sin recortar &&.

El funtor correcto debe ser:

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

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

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


 0
Author: user396672,
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
2011-02-02 11:28:08

Podría usar una expresión lambda.

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

Esta parece ser la opción más simple.

 0
Author: Puppy,
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
2011-02-02 11:31:14

Una mejora más en la respuesta de GManNickG y tengo una bonita solución:

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

(funciona en gcc - 4.9.2 y msvc2013)

 0
Author: magras,
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
2015-07-15 17:16:33