Подтвердить что ты не робот

Передача rvalues ​​через std:: bind

Я хочу передать rvalue через std::bind в функцию, которая принимает ссылку rvalue в С++ 0x. Я не могу понять, как это сделать. Например:

#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())
}
4b9b3361

Ответ 1

Причина, по которой это не удается, заключается в том, что когда вы указываете foo<Movable>, функция, с которой вы привязываете,:

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

Однако значение, переданное std::bind, не будет значением r, а lvalue (хранится как член где-то в результирующем функторе bind). То, что сгенерированный функтор сродни:

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

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

    Movable arg0;
};

Построено как your_bind(Movable()). Таким образом, вы можете увидеть, что это не удается, потому что Movable&& не может привязываться к Movable. †

Вместо этого может быть простое решение:

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

Поскольку теперь вы вызываете функцию:

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

И звонок работает нормально (и, конечно, вы можете сделать это foo<const Movable&>, если хотите). Но интересным вопросом является то, что мы можем заставить ваше оригинальное связывание работать, и мы можем с помощью:

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

То есть, мы просто std::move аргумент перед тем, как сделать вызов, чтобы он мог связываться. Но яки, это уродливо. Приведение требуется, потому что std::move является перегруженной функцией, поэтому мы должны указать, какую перегрузку мы хотим, выполнив листинг на желаемый тип, исключив другие параметры.

На самом деле это было бы не так плохо, если бы std::move не перегружался, как будто у нас было что-то вроде:

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


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

Это намного проще. Но, если у вас нет такой функции, я думаю, вам ясно, что вы просто хотите указать более явный аргумент шаблона.


† Это отличается от вызова функции без явного аргумента шаблона, потому что явно указывая ее, исключается возможность ее вывода. (T&&, где T является параметром шаблона, может быть выведено что угодно, если вы допустили его.

Ответ 2

Ребята, я взломал совершенную версию пересылки связующего (ограничен 1 параметром) здесь http://code-slim-jim.blogspot.jp/2012/11/stdbind-not-compatable-with-stdmove.html

Для справки код

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_));
  }
};

Как видно из приведенного выше доказательства концепции, его очень возможно...

Я не вижу причин, по которым std:: bind несовместимо с std:: move... std:: forward - это, в конце концов, идеальная пересылка. Я не понимаю, почему нет std:: forwarding_bind???

Ответ 3

(На самом деле это комментарий к ответу GMan, но мне нужно некоторое форматирование для кода). Если сгенерированный функтор действительно такой:

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

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

    Movable arg0;
};

то

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

исправляет ошибки. поскольку можно назначить и инициализировать данные с помощью rvalue, а затем передать значение данных аргументу rvalue для foo().
Однако, я полагаю, что bind bind извлекает функцию аргумента типа непосредственно из foo() подписи. то есть сгенерированный функтор:

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

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

    Movable&& arg0;
};

и действительно, это действительно не позволяет инициализировать член данных rvalue. Возможно, привязка bind просто неправильно извлекает тип "unreferenced" из типа аргумента функции и использует этот тип для объявления члена-объекта-члена "как есть", без обрезки & &.

правильный функтор должен быть:

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

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

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


Ответ 4

Вы можете использовать выражение лямбда.

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

Казалось бы, это самый простой вариант.

Ответ 5

Еще одно улучшение в ответе GManNickG, и у меня есть довольно эффективное решение:

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

(работает в gcc-4.9.2 и msvc2013)