Search code examples
c++c++11bindfunctormem-fun

Why Can't I use a mem_fn Functor in bind?


I wanted to pass a mem_fn argument to bind but the compiler doesn't seem to allow it.

For example this works fine:

accumulate(cbegin(foos), cend(foos), 0, bind(plus<int>(), placeholders::_1, bind(&foo::r, placeholders::_2)));

But when I try to use the mem_fn functor I get about a page of errors:

accumulate(cbegin(foos), cend(foos), 0, bind(plus<int>(), placeholders::_1, mem_fn(&foo::r)));

/usr/include/c++/6/bits/stl_numeric.h: In instantiation of ‘_Tp std::accumulate(_InputIterator, _InputIterator, _Tp, _BinaryOperation) [with _InputIterator = __gnu_cxx::__normal_iterator >; _Tp = int; _BinaryOperation = std::_Bind(std::_Placeholder<1>, std::_Mem_fn)>]’:
prog.cpp:20:102: required from here
/usr/include/c++/6/bits/stl_numeric.h:154:22: error: no match for call to ‘(std::_Bind(std::_Placeholder<1>, std::_Mem_fn)>) (int&, foo* const&)’


Solution

  • To understand this, think about what it would mean if you just passed a literal to bind's 3rd argument. For Example if you had done:

    accumulate(cbegin(foos), cend(foos), 0, bind(plus<int>(), placeholders::_1, 13))
    

    The result would have been size(foos) * 13, because plus would have used 13 as it's addend on each iteration.

    accumulate(cbegin(foos), cend(foos), 0, bind(plus<int>(), placeholders::_1, mem_fn(&foo::r)))
    

    Won't compile because it's attempting to pass the result of mem_fn(&foo::r) as the addend to plus. Since that can't be converted to an int plus can't accept that. But even if it could be converted to an int, that's not what you're looking for, you want to take the 2nd argument and call foo::r on it, passing the result to plus. Thus we know we need to see, placeholders::_2 used somewhere in the statement, conveying the 2nd argument for the calling of it's r method.


    We need to bind placeholders::_2 to be bound to a functor which will call the r method on it's parameter. The binding will of course require bind, but actually bind can take a method as it's 1st argument.

    That said, the bind(&foo::r, placeholders::_2) statement from your working code doesn't make any sense in non-nested form; that functor doesn't even take 2 parameters! actually has special rules for handling a bind nested within another bind, so that they can share the outer bind's placeholders, lest there be no way to convey a bound argument to a nested expression:

    If the stored argument arg is of type T for which std::is_bind_expression<T>::value == true (for example, another bind expression was passed directly into the initial call to bind), then bind performs function composition: instead of passing the function object that the bind subexpression would return, the subexpression is invoked eagerly, and its return value is passed to the outer invokable object. If the bind subexpression has any placeholder arguments, they are shared with the outer bind.


    The only way to use mem_fn in this expression would be to pass it it's result to bind for the conveying of placeholders::_2: bind(mem_fn(&foo::r), placeholders::_2) This works, but is an unnecessary step when the simple bind(&foo::r, placeholders::_2) will suffice. Thus the best way to generate this functor is either with your proffered statement:

    accumulate(cbegin(foos), cend(foos), 0, bind(plus<int>(), placeholders::_1, bind(&foo::r, placeholders::_2)))
    

    Or by using a lambda:

    accumulate(cbegin(foos), cend(foos), 0, [](const int augend, const auto& addend) { return augend + addend.r(); } )