Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use mem_fn?

Tags:

c++

c++11

stdbind

I'm confused as to why std::mem_fn is needed.

I have a function that takes in any callable (lambda, function pointer, etc),and binds it to an argument.

Eg:

template<class T>
void Class::DoBinding(T callable) {
  m_callable = std::bind(callable, _1, 4);
}
//somewhere else
Item item;
m_callable(item);

All code samples I've seen do:

//some defined member function
Item::Foo(int n);

DoBinding(std::mem_fn(&Item::Foo));

Why can't it simply be:

DoBinding(&Item::Foo);

It seems the latter is callable without having to use std::mem_fn, so why is it needed?

like image 472
Jonathan. Avatar asked May 16 '16 17:05

Jonathan.


2 Answers

This is because generic code that expects UnaryFunction or BinaryFunction will invoke it directly with the regular call syntax. So to pick an arbitrary algorithm like for_each, it could well be implemented like:

template<class InputIt, class UnaryFunction>
UnaryFunction for_each(InputIt first, InputIt last, UnaryFunction f)
{
    for (; first != last; ++first) {
        f(*first); // <== N.B. f(*first)
    }
    return f;
}

If you called for_each() with &Item::Foo, the code try to call (&Item::Foo)(x), which is ill-formed since for pointers to members you have to write (x.*&Item::Foo)(). It's that syntactical difference that mem_fn is meant to solve: mem_fn deals with the invocation syntax of pointers to members so that you can use all the algorithms with pointers to members as well as functions and function objects. You cannot have for_each(v.begin(), v.end(), &Item::Foo) but you can have for_each(v.begin(), v.end(), mem_fn(&Item::Foo)).

This works just fine in std::bind() (and std::thread and std::function and ...) natively since those all have explicit handling for pointers to members separately. And since DoBinding() itself calls std::bind(), there is no reason for std::mem_fn in this case.


There is was a proposal to get rid of this syntactic difference: P0312. It did not go well.

like image 127
Barry Avatar answered Sep 24 '22 20:09

Barry


This is usually done because the person who writes DoBinding(std::mem_fn(&Item::Foo)) has no idea that DoBinding can take a member pointer directly.

Remember: std::sort(..., &Item::Foo) will fail, because sort expects the value to be a directly-callable function object. And member pointers are not. Indeed, pretty much every algorithm in the C++ standard library will fail when given a member pointer instead of a directly-callable type. Your DoBinding only works because you're using std::bind, which has a special overload for member pointers. The caller of DoBinding doesn't necessarily know you're doing that.

Most code that takes callables by template parameters will choke on a member pointer. So to be safe, we don't pass member pointers around as objects that can be directly called; use mem_fn to turn it into such an object.

like image 26
Nicol Bolas Avatar answered Sep 22 '22 20:09

Nicol Bolas