2017-02-18 9 views
13

Oto pełny przykład:Dlaczego ogólne wywołanie lambda nie jest możliwe, ale można je zawijać w klasie?

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 
} 

Dlaczego jest to możliwe za pomocą owijki, robiąc to bezpośrednio powoduje następujący błąd?

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);}; 
            ~~~~^~~~~~ 
+0

Twój OK powinien nadal mieć błąd, ponieważ 'callSelf' ma przechwytywanie, gdy nie jest niczym lokalnym. Zredukujesz swój kod do przykładu, który wprowadza nowy błąd. – chris

+0

@chris oh, przepraszam, caputre nie jest tu konieczne, edycja. Ale przy przechwytywaniu powinno to spowodować błąd podczas kompilacji lub źle sformułować program? – xinaiz

+0

Ten komunikat o błędzie informuje dokładnie, dlaczego. Co jest niejasnego w tej kwestii? –

Odpowiedz

16

Jest to dość skomplikowane. Reguła używasz afoul jest w [dcl.spec.auto]:

Jeśli konieczna jest rodzaj podmiotu z undeduced rodzaju zastępczego w celu określenia rodzaju wyrażenia, program jest złego powstały.

To, co się dzieje źle tutaj:

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

Musimy znać rodzaj callSelf określić rodzaj ekspresji func(func), która go iself kołowym. Jest łatwo rozpoznawana po prostu określając rodzaj powrotu:

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

Jednak gdy owinąć lambda, można uzyskać różne zachowanie. Ta linia tutaj:

w(w); 

przechodzi obiekt typu wrapper się, skutecznie, lambda. To nie jest jego własny typ. Ciało lambda wywołuje ten obiekt sam w sobie, ale znamy typ tego wyrażenia. Ci ogłosił go:

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

Funkcja ta działa (z jakiegoś definicji robót) z void z tego samego powodu lambda pracował kiedy dodaliśmy -> void. To już nie jest nieużywany symbol zastępczy. Znamy już typ zwrotu. Aby uzyskać takie samo zachowanie, jak w przypadku lambda, zmień deklarację operator() na auto.

8

W twoim przypadku, po prostu określić typ zwracanej i kompilator powinien to zaakceptować:

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 
} 

z typem powrotu odliczenia, kompilator nie może używać lambda w samej lambda ponieważ kompilator musi zobaczyć ciało funkcji do dedukcji typu zwrotu. Fakt, że kompilator musi sprawdzić treść tej funkcji, sprawia, że ​​widzi ona zawartość twojej lambda, która używa samej lambdy. Ponieważ kompilator jest w procesie dedukcji, nie można użyć lambda, stąd błąd kompilacji.

+0

Dlaczego nie jest tak, że jeśli nie ma instrukcji 'return', funkcja z wyprowadzonym typem zwracanym zwraca typ' void'? W przeciwnym razie, w jaki sposób można przekonać kompilator, że lambda zwróciła typ 'void' przez to, że go dedukuje? Wydaje się, że jeśli ciałem jest 'if (0) return void(); func (func); ', to działa. Ale czy istnieje lepsze rozwiązanie? –

+0

Tak, ale aby sprawdzić, czy nie ma instrukcji return, kompilator musi jeszcze sprawdzić treść tej funkcji, a całe ciało funkcji musi być dobrze zdefiniowane. Ponieważ w ciele używasz funkcji, której typ zwrotu jest jeszcze określony, ciało funkcji jest źle sformułowane i kompilacja zawodzi. –

+0

Ah Rozumiem teraz, dzięki. –

Powiązane problemy