Передача значений через std :: bind - PullRequest
24 голосов
/ 02 февраля 2011

Я хочу передать значение через std::bind функции, которая принимает ссылку на значение в C ++ 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())
}

Ответы [ 5 ]

28 голосов
/ 02 февраля 2011

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

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

Однако значение, передаваемое std::bind, будет не r-значением, а l-значением (хранится как член где-то в результирующем функторе 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 - параметр шаблона, может быть выведен в что угодно , , если вы позволите ему быть .)

1 голос
/ 11 ноября 2012

Ребята, я взломал идеальную версию пересылки связующего (ограничено 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 ???

0 голосов
/ 15 июля 2015

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

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

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

0 голосов
/ 02 февраля 2011

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

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

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

0 голосов
/ 02 февраля 2011

(Это на самом деле комментарий к ответу 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 извлекает тип аргумента функции непосредственно из сигнатуры 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. Возможно, имплементация связывания просто не правильно извлекает тип «unreferenced» из типа аргумента функции и использует этот тип для объявления члена данных функтора «как есть», без усечения &&.

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

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

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

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


...