Подтвердить что ты не робот

Почему не может быть общий вызов лямбды, но это может сделать его упаковка в классе?

Вот полный пример:

auto callSelf = [](auto& func) {func(func);};

class wrapper : public decltype(callSelf) {
    using base = decltype(callSelf);
public:
    wrapper() : base(callSelf) {}

    template<class T>
    void operator()(T& func) {
        base::operator()(func);
    }
};

int main()
{
    //callSelf(callSelf); // Error
    wrapper w;
    w(w); // OK, nice endless recursion
}

Почему это возможно с оберткой, при этом она вызывает непосредственно ошибку?

main.cpp:30: error: use of '<lambda(auto:1&)> [with auto:1 = <lambda(auto:1&)>]' before deduction of 'auto'
 auto callSelf = [&](auto& func) {func(func);};
                                  ~~~~^~~~~~
4b9b3361

Ответ 1

Это действительно довольно сложно. Правило, в котором вы работаете, находится в [dcl.spec.auto]:

Если тип объекта с неопределенным типом-заполнителем необходим для определения типа выражения, программа плохо сформирована.

Вот что здесь не так:

auto callSelf = [](auto& func) {func(func);};
callSelf(callSelf);

Нам нужно знать тип callSelf, чтобы определить тип выражения func(func), который он является круговым. Это легко устранить, просто указав тип возвращаемого значения:

auto callSelf = [](auto& func) -> void {func(func);};
callSelf(callSelf); // ok. I mean, infinite recursion, but otherwise ok. ish.

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

w(w);

передает объект типа wrapper в, эффективно, лямбда. Это не его собственный тип. Тело лямбды вызывает этот объект сам по себе, но мы знаем тип этого выражения. Вы заявили об этом:

template<class T>
void operator()(T& func) {
~~~~~

Эта функция работает (для некоторого определения работ) с void по той же причине, что и лямбда, когда мы добавили -> void. Он больше не является неподтвержденным заполнителем. Мы уже знаем тип возврата. Чтобы получить то же поведение, что и с лямбдой, измените объявление operator() на auto.

Ответ 2

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

auto callSelf = [](auto& func) -> void {func(func);};

class wrapper : public decltype(callSelf) {
    using base = decltype(callSelf);
public:
    wrapper() : base(callSelf) {}

    template<class T>
    void operator()(T& func) {
        base::operator()(func);
    }
};

int main()
{
    callSelf(callSelf); //works
    wrapper w;
    w(w); //ok, nice endless recursion
}

С выводом типа возврата компилятор не может использовать лямбда в самой лямбда, потому что компилятор должен видеть тело функции для вывода возвращаемого типа. Тот факт, что компилятор должен проверить тело функции, заставляет его видеть содержимое вашей лямбды, которая использует сам лямбда. Поскольку компилятор находится в процессе вычитания, вы не можете использовать lambda, следовательно, ошибку компиляции.