Спецификация лямбда-возвращаемого значения не работает - PullRequest
1 голос
/ 29 июня 2019

У меня проблема. Функция "__sub" анализирует строку типа "1x + (5y - 2)". Каждый раз, когда он видит «(», он вызывает себя, чтобы точно разобрать, что находится в скобках. Вот некоторый псевдокод, иллюстрирующий проблему:

auto __sub = [&needed_fn](const char *& iter, char end_at) -> int {
  for (; *iter != end_at; iter++) {
    if () {
      int number = needed_fn(iter);
    } else if (*iter == '(') {
      int sub_result = __sub(iter, ')');
    }
  }
  return 0; // temporarily, as for debugging purposes only needed
};

Но это не работает. Сначала не было спецификации (-> int). И это не работает в обоих случаях с или без этой спецификации возвращаемого значения.

Там написано:

a.cpp: In lambda function:
a.cpp:97:22: error: use of ‘__sub’ before deduction of ‘auto’
 int sub_result = __sub(it, ')');

Ответы [ 2 ]

2 голосов
/ 29 июня 2019

Предложение: определите __sub как std::function<int(const char *, char)>

std::function<int(const char * &, char)> __sub;

__sub = [&needed_fn](const char *& iter, char end_at) -> int {
  for (; *iter != end_at; iter++) {
    if ( /* ??? */ ) {
      int number = needed_fn(iter);
    } else if (*iter == '(') {
      int sub_result = __sub(iter, ')');
    }
  return 0;
};

, иначе компилятор не сможет определить (auto) тип __sub(), используя тот же __sub() внутри тела__sub().

1 голос
/ 29 июня 2019

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

Чтобы немного упростить обсуждение, возьмем общий рекурсивный пример, факториал ( 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);
};

Надеюсь, это поможет!

...