Создание функтора из лямбда-выражения

Я хотел бы знать, можно ли создать реальный объект функтора из лямбда-выражения. Я так не думаю, но если нет, то почему?

Чтобы проиллюстрировать, приведенный ниже код сортирует точки с использованием различных политик для координат x и y:

#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"; 
                  });
}

Выражение std::sort(pts.begin(), pts.end(), SortXY<Descending, Ascending>()); сортируется по убыванию значений x, а затем по возрастанию значений y. Это легко понять, и я не уверен, что действительно хочу использовать здесь лямбда-выражения.

Но если бы я захотел заменить Ascending/Descending на лямбда-выражения, как бы вы это сделали? Следующее недопустимо:

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

person Daniel Gehriger    schedule 26.01.2011    source источник
comment
Возможно, вам нужна функция make_sortXY для вывода аргументов шаблона? См. make_pair. Это предполагает, что С++ 0x позволяет безымянному типу лямбды вообще быть аргументом шаблона, я не знаю.   -  person Steve Jessop    schedule 26.01.2011
comment
Также лямбда-выражение приводит к созданию объекта с operator(). В общем случае для этого требуется экземпляр, потому что именно там хранятся любые захваченные переменные, и я не уверен, определяется ли каким-либо образом лямбда без захвата как особый случай. В частности, есть ли у его типа доступный конструктор без аргументов, который вы использовали?   -  person Steve Jessop    schedule 26.01.2011
comment
@Steve: спасибо за ваши комментарии. Я понимаю, что вы имеете в виду, кроме последнего предложения в вашем втором комментарии. Что вы имеете в виду?   -  person Daniel Gehriger    schedule 26.01.2011
comment
когда вы пишете XOrder()(lhs.x, rhs.x), вы вызываете конструктор без аргументов XOrder (ну, или другой конструктор, все параметры которого являются необязательными), а затем вызываете operator() для результирующего временного объекта. Я не знаю, потому что я не смотрел, можете ли вы просто создать экземпляр лямбды из ее типа.   -  person Steve Jessop    schedule 26.01.2011
comment
@Steve: Думаю, нет, так как вы не можете получить тип лямбда.   -  person Daniel Gehriger    schedule 26.01.2011


Ответы (3)


Эта проблема возникает из-за того, что SortXY принимает только типы, тогда как лямбда-выражения являются объектами. Вам нужно переписать его так, чтобы он принимал объекты, а не только типы. Это базовое использование функциональных объектов — посмотрите, как std::for_each принимает не тип, а объект.

person Puppy    schedule 26.01.2011
comment
Да, я понимаю это. Мне пришлось бы изменить SortXY и добавить конструктор, а затем использовать его как SortXY(Descending(), Ascending()). Это сильно усложняет код (конструктор, переменные-члены в SortXY,...). Я бы предпочел использовать `typeof(/* лямбда-выражение */)... - person Daniel Gehriger; 26.01.2011
comment
Вы не можете. У лямбда-выражений совершенно неопределенные типы — вы даже не можете decltype лямбда-выражения. Невозможно получить тип шаблона лямбда без использования вывода типа для этого точного лямбда-идентичного определения неприемлемо. Честно говоря, я признаю, что ваш текущий код проще, но это просто плохая практика — как только вы захотите добавить туда какое-либо состояние, вам конец. Вы пишете SortXY только один раз, но можете вызывать его десятки раз. - person Puppy; 26.01.2011

Я разместил аналогичный вопрос w.r.t. лямбда-функторы внутри классов. Проверьте это, возможно, это поможет:

Лямбда-выражение как функтор-член в классе

person gilgamash    schedule 14.04.2015

У меня была аналогичная проблема: в некоторых случаях требовалось предоставить «сырой» указатель на функцию, а в других — функтор. Поэтому я придумал «обходной путь» следующим образом:

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);
};

Предполагая, что у вас есть две очень простые функции, принимающие --- как описано --- либо функтор, либо необработанный указатель функции, например:

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;
}

Затем вы бы назвали эти функции следующим образом:

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;

Конечно, в этом примере нет никакой реальной пользы от передачи int в качестве ссылки... это просто пример :-)

Улучшения:

Вы также можете изменить класс Selector, чтобы он был более лаконичным:

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;
};

Здесь мы используем простой typedef, чтобы определить указатель функции один раз и использовать только его имя, а не писать объявление снова и снова.

person Alessandro Giusa    schedule 19.07.2017