Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't a generic lambda call itself, but wrapping it in a class allows it?

Here is the full example:

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
}

Why is it possible with the wrapper, while doing it directly causes the following error?

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);};
                                  ~~~~^~~~~~
like image 845
xinaiz Avatar asked Feb 18 '17 22:02

xinaiz


2 Answers

This is actually quite tricky. The rule you're running afoul of is in [dcl.spec.auto]:

If the type of an entity with an undeduced placeholder type is needed to determine the type of an expression, the program is ill-formed.

That is what's going wrong here:

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

We need to know the type of callSelf to determine the type of the expression of func(func), which it iself circular. This is easily resolvable by simply specifying the return type:

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

However, when you wrap the lambda, you get different behavior. This line here:

w(w);

is passing an object of type wrapper into, effectively, the lambda. That is not its own type. The body of the lambda invokes that object on itself, but we know the type of that expression. You declared it:

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

This function works (for some definition of works) with void for the same reason the lambda worked when we added -> void. It's no longer an undeduced placeholder. We already know the return type. To get the same behavior as with the lambda, change the declaration of operator() to be auto.

like image 76
Barry Avatar answered Nov 14 '22 15:11

Barry


In your case, simply define the return type and the compiler should accept it:

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
}

With return type deduction, the compiler cannot use the lambda in the lambda itself because the compiler has to see the body of the function to deduce the return type. The fact that the compiler has to check the body of the function make it see the content of your lambda that uses the lambda itself. Since the compiler is in the deduction process, you cannot use the lambda, hence the compilation error.

like image 30
Guillaume Racicot Avatar answered Nov 14 '22 17:11

Guillaume Racicot