简体   繁体   中英

C++ how to convert member functions to standalone functions (for use as function parameter) generically?

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 ?

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM