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

236 views Asked by At

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&)’

2

There are 2 answers

0
Jonathan Mee On BEST ANSWER

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(); } )
0
Igor Tandetnik On

Well, clearly, the second example doesn't mention placeholders::_2. When accumulate calls the functor with two arguments, the second argument is ignored, and your code is trying to add up an int and an instance of the internal class that mem_fn returns.

I suggest you drop all these bind games, and use a lambda:

accumulate(cbegin(foos), cend(foos), 0, 
    [](int val, foo* f) { return val + f->r(); });

Much clearer what's going on here.