Я собираюсь не согласиться с утверждением, что это проблема курицы и яйца, или, по крайней мере, это решаемо, и предположить вместо этого, что это извращение языка, потому что вы можете выполнить практически то же самое от руки.
Чтобы немного упростить обсуждение, возьмем общий рекурсивный пример, факториал ( godbolt ):
auto factorial = [](int n) {
if (n == 0)
return 1;
else
return n * factorial(n - 1);
};
Сбой с появившейся ошибкой:
<source>: In lambda function:
<source>:7:24: error: use of 'factorial' before deduction of 'auto'
7 | return n * factorial(n-1);
| ^~~~~~~~~
Но factorial
- это переменная продолжительности автоматического хранения, поэтому вы не можете ссылаться на нее, не захватив ее, и код должен быть неверный без захвата. Захват по значению не имеет смысла, поскольку лямбда-тип будет содержать свою копию. Это было бы несовместимо с типичными классами C ++, которые не могут содержать своих копий, даже если в противном случае они пусты. Таким образом, нужно захватить по ссылке ( Godbolt ):
auto factorial = [&factorial](int n) {
if (n == 0)
return 1;
else
return n * factorial(n - 1);
};
Наш код теперь более правильный. Что говорит компилятор?
<source>:3:24: error: use of 'factorial' before deduction of 'auto'
3 | auto factorial = [&factorial](int n) {
| ^~~~~~~~~
<source>: In lambda function:
<source>:7:24: error: use of 'factorial' before deduction of 'auto'
7 | return n * factorial(n - 1);
| ^~~~~~~~~
Больше ошибок! Лямбда - это всего лишь синтаксический сахар для функционального объекта, поэтому давайте сделаем шаг назад и посмотрим, сработает ли неагрессированная форма ( godbolt ):
struct factorial_t
{
factorial_t& factorial;
auto operator()(int n) const
{
if (n == 0)
return 1;
else
return n * factorial(n - 1);
}
};
int main()
{
factorial_t factorial{factorial};
}
Это работает, и в идеальном мире, может быть, и лямбда-форма тоже. До того, как auto
in factorial
будет выведено, он очень похож на неполного типа. Ссылки и указатели на неполные типы разрешены в C ++, включая ссылки на класс или структуру, которая их содержит. А лямбда-ссылки - это просто ссылки или указатели. Так что все это возможно в духе языка. Другой язык может определить тип factorial
для типа лямбды, в то время как тип лямбды является неполным, т. Е. Перед попыткой создать определение для типа лямбда.
В C ++ у вас есть несколько возможных решений. Во-первых, вы можете написать тип замыкания вручную (как в третий пример ).
Во-вторых, вы можете стереть тип, как в другом ответе ( godbolt ):
std::function<int(int)> factorial = [&factorial](int n) {
if (n == 0)
return 1;
else
return n * factorial(n - 1);
};
Обратите внимание, что в другом ответе отсутствовал захват, который является ключевым.
В-третьих, вы можете отложить необходимость для типа ( Godbolt ):
auto factorial = [](int n, auto&& factorial) {
if (n == 0)
return 1;
else
return n * factorial(n - 1, factorial);
};
Это задерживает необходимость в типе, делая оператор вызова шаблоном за счет неудобного использования, например. factorial(4, factorial)
. Даже это преодолимо с небольшим уровнем косвенности ( Godbolt ):
auto factorial_impl = [](int n, auto&& factorial_impl) {
if (n == 0)
return 1;
else
return n * factorial_impl(n - 1, factorial_impl);
};
auto factorial = [&factorial_impl](int n) {
return factorial_impl(n, factorial_impl);
};
Надеюсь, это поможет!