2015-07-08 11 views
5

Когда я назначаю лямбда явно типизированной переменной (например, когда она рекурсивна, чтобы сама захватить функцию), я использую std::function.Существует ли тип std :: function или аналогичный для лямбда с автоматическим параметром?

Рассмотрит это глупые «битый подсчет» функции в качестве примера:

std::function<int(int)> f; 
f = [&f](int x){ return x ? f(x/2)+1 : 0; }; 

Что о случае, когда мы используем параметр автоматического обобщать x, который был внесен в C++ 14 родовой лямбды?

std::function<int(???)> f; 
f = [&f](auto x){ return x ? f(x/2)+1 : 0; }; 

Очевидно, что я не могу поставить auto в параметрах function типа.

Есть ли возможность определить класс функтора, достаточно общий для покрытия точного случая выше, но все еще используя лямбда для определения функции?

(Не переопределяйте это, принимайте только один автоматический параметр и код жесткого кода для возвращаемого значения.) Вариант использования будет для сценария, описанного выше: захват функции сам по себе посредством ссылки для рекурсивных вызовов.

+0

Проблема здесь, я думаю, заключается в том, что функция lamba нуждается в определенном типе - 'auto' просто позволяет компилировать вычисление самостоятельно. Это не означает, что вы можете передать лямбда-функцию любому коду с любым типом - тип должен быть известен во время компиляции лямбда-функции. –

+1

Как насчет 'auto & f = [& f] (auto x) {return x? f (x/2) +1: 0;}; '? Это работает? (Возможно, просто 'auto' вместо' auto & '.) У меня нет компилятора с поддержкой C++ 14, поэтому я не могу его легко проверить. – celticminstrel

+0

'std :: function' использует стирание типа. Я совершенно уверен, что принципиально невозможно * создать объект-стираемый объект-объект, который принимает произвольные типы, поскольку каждый набор типов аргументов должен * создавать * новую функцию, которая может быть выполнена только в том случае, если компилятор может видеть завернутый * шаблон функции *. Методы обхода включают использование фиксированного набора наборов типов параметров (используйте существующие наборы перегрузок/precompute всех перегрузок до стирания типа) или тип-стирание аргументов. – dyp

ответ

4

Вы можете создать лямбда, который называет себя, пропуская его к себе в качестве параметра:

auto f = [](auto self, auto x) -> int { 
    return x ? self(self, x/2) + 1 : 0; 
}; 

std::cout << f(f, 10); 

Вы можете захватить, что лямбда в другом лямбда, так что вам не придется беспокоиться о прохождении его сам:

auto f2 = [&f](auto x) { 
    return f(f, x); 
}; 

std::cout << f2(10); 
+0

Хотя вы не отвечаете на фактический вопрос (я хотел ввести имя типа для функтора), я предполагаю, что могу использовать его для решения моей актуальной проблемы (что слишком долго для объяснения - я думал, что мне нужно явно указать functor a type, но я думаю, что я могу обойти это с вашим решением в моем конкретном сценарии). – leemes

3

Вот быстрый у-комбинатор на основе рекурсивный двигатель:

template<class F> 
struct recursive_t { 
    F f; 

    // note Self must be an lvalue reference. Things get 
    // strange if it is an rvalue: 
    // invoke makes recursive ADL work a touch better. 
    template<class Self, class...Args> 
    friend auto invoke(Self& self, Args&&...args) 
    -> decltype(self.f(self, std::declval<Args>()...)) 
    { 
    return self.f(self, std::forward<Args>(args)...); 
    } 
    // calculate return type using `invoke` above: 
    template<class Self, class...Args> 
    using R = decltype(invoke(std::declval<Self>(), std::declval<Args>()...)); 

    template<class...Args> 
    R<recursive_t&, Args...> operator()(Args&&...args) 
    { 
    return invoke(*this, std::forward<Args>(args)...); 
    } 
    template<class...Args> 
    R<recursive_t const&, Args...> operator()(Args&&...args)const 
    { 
    return invoke(*this, std::forward<Args>(args)...); 
    } 
}; 

template<class F> 
recursive_t< std::decay_t<F> > recurse(F&& f) 
{ 
    return {std::forward<F>(f)}; 
} 

теперь вы можете сделать:

auto f = recurse([](auto&& f, auto x){ return x ? f(x/2)+1 : 0; }); 

и вы получите рекурсивное лямбда, не имеют & захват (что ограничивает его использование в текущей области видимости).

Захват std::function по ссылке означает, что ваше время жизни лямбда - это текущая область действия, и каждый рекурсивный вызов требует перехода на стирание типа (блокирование любой возможной оптимизации, например хвостовой рекурсии, по рекурсивному вызову). То же самое относится к другим аналогичным решениям.

Необходимо использовать recursive_t, а не использовать лямбда, поскольку лямбда не может называть себя самой.

Live example.

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

template<class F> 
auto recurse(F&& f) { 
    return [f=std::forward<F>(f)](auto&&...args){ 
    return f(f, decltype(args)(args)...); 
    }; 
}; 

В recursive_t работает как:

auto fib = recurse([](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); }); 

версия лямбда работает как:

auto fib = recurse([](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); }); 

, который я , лично, найти более неудобно.

Это также сложнее описать тип recurse. Для версии recursive_t, recurse имеет тип:

((A->B)->A->B)->(A->B) 

что неудобно, но конечный тип.

Лямбда-версия сложнее. Тип функции аргумента recursive имеет тип:

F:= F->A->B 

который раздражающе бесконечным, а затем recurse имеет тип

F->A->(A->B) 

, который наследует бесконечность.

Во всяком случае, возвращаемое значение recurse может быть сохранено в обыденном std::function или не храниться в контейнере с любым типом.