3

For function parameters of template functions I find myself often wrapping member functions inside a lambda to create identical standalone functions with the first parameter being the object.

A (dummy) example :

class A
{
public:
    double f1() const;
    double f2(int i) const;

    // some data
};

template <typename Func>
double calculateSum(std::vector<A> as, Func f)
{
    double result = 0.0;
    for (auto a : as)
        result += f(a);
    return result;
}

int main()
{
    std::vector<A> as;
    int i = 0;
    auto sum1 = calculateSum(as, [](const A& a) { return a.f1(); });
    auto sum2 = calculateSum(as, [&i](const A& a) { return a.f2(i); });
    return 0;
}

Is there a way to define such lambdas more generically ? or is there a way to directly refer to the member functions instead of using lambdas ?

mr_T
  • 2,353
  • 2
  • 17
  • 33
  • `std::function` with member functions works in the way you explained in the question (but not in they way your code works with `f2`). Not sure if that's any more generic though since you'd still need to specify function type in specialization. – Andrey Turkin Nov 16 '16 at 15:00
  • 2
    You might also want to look at `std::mem_fn` – happydave Nov 16 '16 at 15:01
  • 1
    @happydave aaaaaaand I've just reinvented `std::mem_fn` while composing my answer. Great. – Quentin Nov 16 '16 at 15:31

3 Answers3

4

You can use C++14 generic lambdas to help with this. Define your generic lambda like this:

auto bindMem = [](auto f, auto& ... memArgs) { return [f, &memArgs...](auto& a) { return (a.*f)(memArgs...); }; };

To digest: this creates a generic lambda, the invocation of which produces another lambda. The first lambda gets the member function you want to invoke, with any bound parameters (other than the this object). It produces a second lambda, which just then expects the object itself and applies the member function call to it with the earlier-bound parameters.

So for your use case, you have the neat-looking:

auto sum1 = calculateSum(as, bindMem(&A::f1));
auto sum2 = calculateSum(as, bindMem(&A::f2,i));

The great thing about this is that exactly the same bindMem lambda will work for any class and any member function, with arbitrary argument lists. So it really is generic in the sense that you meant.

Smeeheey
  • 9,162
  • 16
  • 37
  • This code triggers UB, as `memArgs` are dead by the time you call the returned lambda that captured them by reference. – Quentin Nov 16 '16 at 15:43
  • OK, I've changed to capture them by value instead – Smeeheey Nov 16 '16 at 15:47
  • 1
    Actually better still: I've changed back to reference capture in the inner lambda, but this time the outer lambda takes them by ref preventing UB – Smeeheey Nov 16 '16 at 16:00
3

After some researching, thanks to the suggestion of @happydave, I'll go with the following answer:

auto sum1 = calculateSum(as, std::mem_fn(&A::f1));

The second sum cannot be dealt with as such and should remain a lambda. However, in general, it appears to be the less likely case that the client code is the supplier of the arguments (in which case the arguments need to be passed anyhow to the template function (and a lambda capture is a great way)). In many cases, the template function supplies also the passed function's arguments and std::mem_fn would be fine.

mr_T
  • 2,353
  • 2
  • 17
  • 33
2

You can use std::bind() for this.

Bind takes a function and however many arguments you want to go with it and returns a nice std::function object. You can specify the arguments at creation time or use placeholders to specify them when you're calling the returned function.

#include <functional>
...
auto sum1 = calculateSum(as, std::bind(&A::f1, std::placeholders::_1));
auto sum2 = calculateSum(as, std::bind(&A::f2, std::placeholders::_1, i);

Remember that non-static member functions take a class instance as their first argument (though most of the time it's done implicitly, this is not one of them), this is why we're using a placeholder. When you do f(a) now, that a (the class instance) is substituted for that placeholder.

Further reading: Bind, Placeholders

Alex Meuer
  • 935
  • 18
  • 33
  • 4
    Assuming C++14, I think the general consensus is that [lambdas should always be preferred over std::bind](http://stackoverflow.com/a/17545183/3282436). – 0x5453 Nov 16 '16 at 15:21
  • 1
    @0x5453 Thank you. I actually hadn't realized the disadvantages. – Alex Meuer Nov 16 '16 at 15:24