#опытным
Конечно же вчерашний пост был первоапрельской шуткой. Не волнуйтесь, мы вас не бросим и без контента не оставим.
Поэтому возвращаемся к нашим баранам. То есть рекурсивным лямбдам. В прошлый раз мы узнали, что лямбды не могут захватывать себя, поэтому не могут быть рекурсивными. Сегодня поговорим о способах, как обойти эту проблему.
1️⃣ Вместо использования auto, явно приводим лямбду к std::function. Тогда компилятор будет знать точный тип функционального объекта и сможет его захватить в лямбду:
std::function<int(int)> factorial = [&factorial](int n) -> int {
return (n) ? n * factorial(n-1) : 1;
};Но использование std::function очень затратно по всем критериям. Компиляция ощутимо замедляется, асма намного больше становится, и std::function обычно сильно медленнее обычных функций и лямбд. А еще и динамические аллокации.
Поэтому не самый хороший способ.
2️⃣ Используем С++14 generic лямбды:
auto factorial = [](int n, auto&& factorial) {
if (n <= 1) return n;
return n * factorial(n - 1, factorial);
};
auto i = factorial(7, factorial);Тут надо разобраться. Мы не могли захватывать лямбду в себя, потому что мы не знали ее тип. Сейчас мы тоже не знаем ее тип, но нам это и не нужно, потому что мы используем дженерик лямбду, которая под капотом превращается в замыкание с шаблонным оператором(). Благодаря cppinsides мы можем заглянуть под капот:
class __lambda_24_20
{
public:
template<class type_parameter_0_0>
inline /*constexpr */ auto operator()(int n, type_parameter_0_0 && factorial) const
{
if(n <= 1) {
return n;
}
return n * factorial(n - 1, factorial);
}
#ifdef INSIGHTS_USE_TEMPLATE
template<>
inline /*constexpr */ int operator()<__lambda_24_20 &>(int n, __lambda_24_20 & factorial) const
{
if(n <= 1) {
return n;
}
return n * factorial.operator()(n - 1, factorial);
}
#endif
};
У класса есть шаблонный оператор, но это полностью завершенный тип. После объявления лямбды компилятор уже знает конкретный тип замыкания и может инстанцировать с ним шаблонный метод.
Форма использования такой лямбды оставляет желать лучшего, потому что нам нужно постоянно передавать ее в качестве параметра. Полечить это, как всегда, можно введением дополнительного уровня индирекции. Обернем лямбду в лямбду!
auto factorial_impl = [](int n, auto&& factorial) {
if (n <= 1) return n;
return n * factorial(n - 1, factorial);
};
auto factorial = [&](int n) { return factorial_impl(n, factorial_impl); };
auto i = factorial(7);Теперь не нужно передавать доп параметры.
3️⃣ Если лямбда ничего не захватывает, то ее можно приводить к указателю на функцию. На этом основан следующий метод:
using factorial_t = int(*)(int);
static factorial_t factorial = [](int n) {
if (n <= 1) return n;
return n * factorial(n - 1);
};
auto i = factorial(7);
Статическая локальная переменная видна внутри лямбды, поэтому такой трюк прокатывает.
Если у вас есть какие-то еще подобные приемы - пишите в комменты.
Но это все какие-то обходные пути. Хочется по-настоящему рекурсивные лямбды.
И их есть у меня!
Об этом в следующий раз.
Always find a way out. Stay cool.
#template #cppcore #cpp11 #cpp14