简体   繁体   中英

Creating functor from lambda expression

I would like to know if it is possible to create an actual functor object from a lambda expression. I don't think so, but if not, why?

To illustrate, given the code below, which sorts points using various policies for x and y coordinates:

#include <vector>
#include <functional>
#include <algorithm>
#include <iostream>

struct Point 
{ 
    Point(int x, int y) : x(x), y(y) {}
    int x, y; 
};

template <class XOrder, class YOrder> 
struct SortXY : 
    std::binary_function<const Point&, const Point&, bool>
{
    bool operator()(const Point& lhs, const Point& rhs) const 
    {
        if (XOrder()(lhs.x, rhs.x))
            return true;
        else if (XOrder()(rhs.x, lhs.x))
            return false;
        else
            return YOrder()(lhs.y, rhs.y);
    }          
};

struct Ascending  { bool operator()(int l, int r) const { return l<r; } };
struct Descending { bool operator()(int l, int r) const { return l>r; } };

int main()
{
    // fill vector with data
    std::vector<Point> pts;
    pts.push_back(Point(10, 20));
    pts.push_back(Point(20,  5));
    pts.push_back(Point( 5,  0));
    pts.push_back(Point(10, 30));

    // sort array
    std::sort(pts.begin(), pts.end(), SortXY<Descending, Ascending>());

    // dump content
    std::for_each(pts.begin(), pts.end(), 
                  [](const Point& p) 
                  {
                     std::cout << p.x << "," << p.y << "\n"; 
                  });
}

The expression std::sort(pts.begin(), pts.end(), SortXY<Descending, Ascending>()); sorts according to descending x values, and then to ascending y values. It's easily understandable, and I'm not sure I really want to make use of lambda expressions here.

But if I wanted to replace Ascending / Descending by lambda expressions, how would you do it? The following isn't valid:

std::sort(pts.begin(), pts.end(), SortXY<
    [](int l, int r) { return l>r; }, 
    [](int l, int r) { return l<r; }
>());

This problem arises because SortXY only takes types, whereas lambdas are objects. You need to re-write it so that it takes objects, not just types. This is basic use of functional objects- see how std::for_each doesn't take a type, it takes an object.

I have posted a similar question wrt lambda functors within classes. Check this out, perhaps it helps:

Lambda expression as member functors in a class

I had a similar problem: It was required to provide in some cases a "raw"-function pointer and in other a functor. So I came up with a "workaround" like this:

template<class T>
class Selector
{
public:
    Selector(int (*theSelector)(T& l, T& r))
        : selector(theSelector) {}

    virtual int operator()(T& l, T& r) {
        return selector(l, r);
    }

    int (*getRawSelector() const)(T&, T&) {
        return this->selector;
    }

private:
    int(*selector)(T& l, T& r);
};

Assuming you have two very simple functions taking --- as described --- either a functor or a raw function pointer like this:

int
findMinWithFunctor(int* array, int size, Selector<int> selector)
{
    if (array && size > 0) {
        int min = array[0];
        for (int i = 0; i < size; i++) {
            if (selector(array[i], min) < 0) {
                min = array[i];
            }
        }
        return min;
    }
    return -1;
}

int 
findMinWithFunctionPointer(int* array, int size, int(*selector)(int&, int&))
{
    if (array && size > 0) {
        int min = array[0];
        for (int i = 0; i < size; i++) {
            if (selector(array[i], min) < 0) {
                min = array[i];
            }
        }
        return min;
    }
    return -1;
}

Then you would call this functions like this:

int numbers[3] = { 4, 2, 99 };

cout << "The min with functor is:" << findMinWithFunctor(numbers, 3, Selector<int>([](int& l, int& r) -> int {return (l > r ? 1 : (r > l ? -1 : 0)); })) << endl;


// or with the plain version
cout << "The min with raw fn-pointer is:" << findMinWithFunctionPointer(numbers, 3, Selector<int>([](int& l, int& r) -> int {return (l > r ? 1 : (r > l ? -1 : 0)); }).getRawSelector()) << endl;

Of course in this example there is no real benefit passing the int's as reference...it's just an example :-)

Improvements:

You can also modify the Selector class to be more concise like this:

template<class T>
class Selector
{
public:

    typedef int(*selector_fn)(T& l, T& r);

    Selector(selector_fn theSelector)
        : selector(theSelector) {}

    virtual int operator()(T& l, T& r) {
        return selector(l, r);
    }

    selector_fn getRawSelector() {
        return this->selector;
    }

private:
    selector_fn selector;
};

Here we are taking advantage of a simple typedef in order to define the function pointer once and use only it's name rather then writing the declaration over and over.

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