Автоматический вывод типа с функтором не работает

Возможный дубликат:
std::bind abound function

void foo0(int val) { std::cout << "val " << val << "\n"; }
void foo1(int val, std::function<void (int)> ftor) { ftor(val); }
void foo2(int val, std::function<void (int)> ftor) { ftor(val); }

int main(int argc, char* argv[]) {
    auto                applyWithFoo0       ( std::bind(foo0,     std::placeholders::_1) );
    //std::function<void (int)> applyWithFoo0       ( std::bind(foo0,     std::placeholders::_1) ); // use this instead to make compile
    auto                applyFoo1       (     std::bind(foo1, std::placeholders::_1, applyWithFoo0) );
    foo2(123, applyFoo1);
}

Приведенный выше пример не компилируется, выдавая несколько ошибок, таких как: Error 1 error C2780: '_Ret std::tr1::_Callable_fun<_Ty,_Indirect>::_ApplyX(_Arg0 &&,_Arg1 &&,_Arg2 &&,_Arg3 &&,_Arg4 &&,_Arg5 &&,_Arg6 &&,_Arg7 &&,_Arg8 &&,_Arg9 &&) const' : expects 10 arguments - 2 provided.
Использование закомментированной строки с явным типом компилирует. Кажется, что тип, выведенный auto, неверен. В чем проблема с auto в данном случае?
Платформа: MSVC 10 SP 1, GCC 4.6.1


person Simon1X    schedule 24.07.2012    source источник
comment
Что не так с использованием &foo0 вместо bind(foo0, _1) ? Это уже унарный вызываемый объект   -  person Jonathan Wakely    schedule 24.07.2012
comment
@MSalters: Это действительно похоже на обман. Использование std::function, по-видимому, обеспечивает волшебное преобразование, которое делает то, что хочет OP. В противном случае правая часть не имеет семантики, которую можно было бы ожидать наивно.   -  person Alexandre C.    schedule 25.07.2012
comment
@Jonathan: На самом деле речь идет о карри. Я опустил дополнительные параметры для простоты. Да, я тоже мог бы использовать лямбда-выражения.   -  person Simon1X    schedule 25.07.2012
comment
@MSalters: спасибо за ссылку. Я думаю, вы правы. Не стесняйтесь отмечать его как дубликат.   -  person Simon1X    schedule 25.07.2012
comment
@MSalters: я думаю, ты прав. Жаль, что я не прошел по вашей ссылке, прежде чем ответить на этот.   -  person sellibitze    schedule 25.07.2012


Ответы (2)


Проблема в том, что std::bind обрабатывает «выражение привязки» (например, ваше applyWithFoo0) иначе, чем другие типы. Вместо вызова foo1 с applyWithFoo0 в качестве параметра он пытается вызвать applyWithFoo0 и передать возвращаемое значение в foo1. Но applyWithFoo0 не возвращает ничего, что можно преобразовать в std::function<void(int)>. Цель такой обработки "выражений привязки" состоит в том, чтобы сделать их легко компонуемыми. В большинстве случаев вы, вероятно, не хотите, чтобы выражение привязки передавалось как параметры функции, а только их результаты. Если вы явно заключаете выражение связывания в объект function<>, объект function<> будет просто передан в foo1 напрямую, так как он не является "выражением связывания" и, следовательно, не обрабатывается специально std::bind.

Рассмотрим следующий пример:

#include <iostream>
#include <functional>

int twice(int x) { return x*2; }

int main()
{
  using namespace std;
  using namespace std::placeholders;
  auto mul_by_2 = bind(twice,_1);
  auto mul_by_4 = bind(twice,mul_by_2); // #2
  auto mul_by_8 = bind(twice,mul_by_4); // #3
  cout << mul_by_8(1) << endl;
}

На самом деле это компилируется и работает, потому что вместо того, чтобы дважды передавать функтор, как можно было бы ожидать от выражений связывания № 2 и № 3, bind фактически оценивает переданные выражения связывания и использует результат в качестве параметра функции дважды. . Здесь намеренно. Но в вашем случае вы случайно споткнулись об это поведение, потому что вы действительно хотите, чтобы привязка передала функции сам функтор вместо его оцененного значения. Обертывание функтора в объект function‹>, очевидно, является обходным путем.

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

auto applyFoo1 = bind( foo1, _1, noeval(applyWithFoo0) );

где noeval указывает привязке не оценивать выражение, а передавать его прямо в функцию. Но, возможно, наоборот — явное указание bind передать результат функтора в функцию — было бы лучше:

auto mul_by_8 = bind( twice, eval(mul_by_4) );

Но я думаю, теперь уже слишком поздно для этого...

person sellibitze    schedule 25.07.2012
comment
Boost.Bind предоставляет protect(bind-expr) эквивалент вашего noeval(bind-expr). Требование eval(bind-expr) затруднит написание вложенных выражений, где вы делаете хотите, чтобы они обрабатывались как одно выражение, например. bind(&Y::f, bind(&getY, bind(&getX, _1)). - person Jonathan Wakely; 25.07.2012
comment
@JonathanWakely: Интересно! Я не знал о protect. Спасибо. - person sellibitze; 25.07.2012

Я предполагаю, что круглые скобки вокруг std::bind заставляют синтаксический анализатор думать, что вы объявляете функции с именами applyWithFoo0 и applyFoo1.

std::bind возвращает функтор, тип которого должен быть в состоянии определить auto.

Попробуй это:

 int main(int argc, char* argv[]) {
    auto                applyWithFoo0  =     std::bind(foo0,     std::placeholders::_1);
    //std::function<void (int)> applyWithFoo0        std::bind(foo0,     std::placeholders::_1) ); // use this instead to make compile
    auto                applyFoo1   =    std::bind(foo1, std::placeholders::_1, applyWithFoo0);
    foo2(123, applyFoo1);
}
person HeywoodFloyd    schedule 24.07.2012
comment
Самая досадная ошибка синтаксического анализа? Не помогло. - person Simon1X; 24.07.2012