Почему компилятору требуется возвращаемый тип в следующих лямбдах

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

#include <vector>

int main() {
    int root;
    std::vector<std::vector<int>> graph;

    // ...
    // build the tree as adjacency list starting from root
    // ...

    auto dfs = [&graph](auto self, auto u) {
        // if (empty(graph[u])) {
        //     return;
        // }
        for (auto v : graph[u]) {
            self(self, v);
        }
    };

    dfs(dfs, root);
}
/usr/bin/g++-10 -std=c++20 -Wall -Wextra -Wshadow -pedantic -fdiagnostics-color=always -g -o main main.cpp
main.cpp: In instantiation of ‘main()::<lambda(auto:44, auto:45)> [with auto:44 = main()::<lambda(auto:44, auto:45)>; auto:45 = int]’:
main.cpp:66:18:   required from here
main.cpp:63:17: error: use of ‘main()::<lambda(auto:44, auto:45)> [with auto:44 = main()::<lambda(auto:44, auto:45)>; auto:45 = int]’ before deduction of ‘auto’
   63 |             self(self, v);
      |             ~~~~^~~~~~~~~

Однако, как только я верну эту логику проверки, если пусто, код компилируется без проблем.

Это потому, что в последнем случае инструкция return дает компилятору подсказку, что лямбда возвращает void и, следовательно, ее тип известен в момент вызова self(self, v)?

В более широком смысле, где я могу прочитать и узнать больше о том, как компиляторы обрабатывают лямбда-определения, чтобы не полагаться на свои сообщения об ошибках и знать, когда необходимо объявлять возвращаемый тип, а когда нет?

Что такое vector? Пожалуйста, опубликуйте минимальный воспроизводимый пример.

273K 21.11.2022 17:43

Я вижу инфинитивную рекурсию зависимости. Вызов dfs зависит от вызова dfs. Чтобы узнать тип первого аргумента, вам нужно знать его тип. Слишком много auto-с.

Marek R 21.11.2022 17:57

ИМО было бы лучше создать класс и удалить лямбду. Было бы легче понять.

Marek R 21.11.2022 18:00
Шаблоны Angular PrimeNg
Шаблоны Angular PrimeNg
Как привнести проверку типов в наши шаблоны Angular, использующие компоненты библиотеки PrimeNg, и настроить их отображение с помощью встроенной...
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Создайте ползком, похожим на звездные войны, с помощью CSS и Javascript
Если вы веб-разработчик (или хотите им стать), то вы наверняка гик и вам нравятся "Звездные войны". А как бы вы хотели, чтобы фоном для вашего...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Начала с розового дизайна
Начала с розового дизайна
Pink Design - это система дизайна Appwrite с открытым исходным кодом для создания последовательных и многократно используемых пользовательских...
Шлюз в PHP
Шлюз в PHP
API-шлюз (AG) - это сервер, который действует как единая точка входа для набора микросервисов.
14 Задание: Типы данных и структуры данных Python для DevOps
14 Задание: Типы данных и структуры данных Python для DevOps
проверить тип данных используемой переменной, мы можем просто написать: your_variable=100
1
3
82
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Да, оператор return; — это именно то, что сообщает компилятору возвращаемый тип лямбды. В этом примере оператор говорит, что функция возвращает void.

Без знания типа возвращаемого значения вызов self(self, v); будет некорректным, поскольку вы фактически полагаетесь на вывод auto, не давая компилятору способа понять это.

Язык явно упоминает этот пример в dcl.spec.auto#general-11:

Если переменная или функция с невыведенным типом заполнителя названа выражением ([basic.def.odr]), программа имеет неправильный формат. Однако, как только в функции встречается неотброшенный оператор возврата, тип возвращаемого значения, выведенный из этого оператора, может использоваться в остальной части функции, в том числе в других операторах возврата.

(выделено мной) и предоставляет более простую версию проблемы в вашем коде:

auto sum(int i) {   
  if (i == 1)
    return i;              // sum's return type is int   
  else
    return sum(i-1)+i;     // OK, sum's return type has been deduced 
} 

Это неполное/неправильное. Авто выводило нормально. dfs имеет конкретный выведенный тип и, конечно же, self зависит от него и может упоминаться в том виде, в каком он есть. Проблема в том, что выражение вызова в лямбда-выражении (со всеми конкретными замещенными типами) создает экземпляр оператора вызова, а вывод его возвращаемого типа — это то, что терпит неудачу (зависит от составного выражения, которое является телом лямбда, которое зависит от Звоните оператору). Вы также можете просто указать тип возвращаемого значения. -> void.

Jeff Garrett 21.11.2022 19:10

Это не является неполным/неправильным, auto действительно не удалось вывести типы, потому что функция была вызвана до того, как компилятору было предоставлено что-либо для вывода возвращаемого типа. В примере видно, что return i присутствует перед рекурсивным вызовом функции, следовательно, компилятор знает, что sum возвращает int, и может правильно обработать return sum(i-1)+i. Если вы измените порядок операторов if, вы увидите ошибки компилятора.

nhtrnm 21.11.2022 22:50

В моем примере довольно ясно, что auto в объявлении dfs относится к возвращаемому типу лямбды, или, если быть более точным, возвращаемому типу оператора вызова, но это все равно с точки зрения вывода auto.

nhtrnm 21.11.2022 22:56

Другие вопросы по теме