2012-08-13 6 views
19

Tener una clase como esta:¿Por qué no es posible usar el método privado en una lambda?

class A { 
public: 
    bool hasGrandChild() const; 

private: 
    bool hasChild() const; 
    vector<A> children_; 
}; 

Por qué no es posible utilizar un método privado hasChild() en una expresión lambda se define en el método hasGrandChild() así?

bool A::hasGrandChild() const { 
    return any_of(children_.begin(), children_.end(), [](A const &a) { 
     return a.hasChild(); 
    }); 
} 

compilador emite un error que el método hasChild() es privado dentro del contexto. ¿Hay algún trabajo alrededor?

Editar: Parece que el código como lo publiqué originalmente funciona. Pensé que es equivalente, pero el código que does not work on GCC es de la misma familia:

#include <vector> 
#include <algorithm> 

class Foo; 

class BaseA { 
protected: 
    bool hasChild() const { return !children_.empty(); } 
    std::vector<Foo> children_; 
}; 

class BaseB { 
protected: 
    bool hasChild() const { return false; } 
}; 

class Foo : public BaseA, public BaseB { 
public: 
    bool hasGrandChild() const { 
    return std::any_of(children_.begin(), children_.end(), [](Foo const &foo) { 
     return foo.BaseA::hasChild(); 
     }); 
    } 
}; 

int main() 
{ 
    Foo foo; 
    foo.hasGrandChild(); 
    return 0; 
} 

Parece que hay un problema con los nombres completos como this does not work, pero this works.

+1

El tipo de cierre no tiene relación con su clase 'A', por lo que, naturalmente, no se puede acceder' miembros no públicos de a'. Tampoco puede, dado que su nombre de tipo es incognoscible, por lo que ni siquiera puede convertirlo en un "amigo". –

+2

¿Soy yo o funciona en gcc? http://ideone.com/333qw – pmr

+0

@pmr: Sí, parece funcionar en GCC anterior, pero no funciona en uno más nuevo. –

Respuesta

27

Parece ser sólo un error GCC en un caso especial cuando el lambda intenta acceder a un miembro protegido de la clase padre usando el nombre completamente calificado. This does not work:

class Base { 
protected: 
    bool hasChild() const { return !childs_.empty(); } 
    std::vector<Foo> childs_; 
}; 

class Foo : public Base { 
public: 
    bool hasGrandChild() const { 
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) { 
     return foo.Base::hasChild(); 
    }); 
    } 
}; 

, pero this works:

class Foo : public Base { 
public: 
    bool hasGrandChild() const { 
    return std::any_of(childs_.begin(), childs_.end(), [](Foo const &foo) { 
     return foo.hasChild(); 
    }); 
    } 
}; 

Según C++ 11, 5.1.2/3:

El tipo de la lambda-expresión (que es también el tipo del objeto de cierre ) es un tipo de clase no sindicalizado exclusivo y sin nombre, denominado tipo de cierre , cuyas propiedades se describen a continuación. Este tipo de clase no es un agregado (8.5.1). El tipo de cierre se declara en el ámbito de bloque más pequeño , alcance de clase o ámbito de espacio de nombres que contiene la correspondiente expresión lambda.

Y entonces C++ 11, 11,7/1:

una clase anidada es miembro y como tal tiene los mismos derechos de acceso como cualquier otro miembro.

Por lo tanto, la función lambda local de la función debe tener los mismos derechos de acceso que cualquier otro miembro de la clase. Por lo tanto, debería ser capaz de llamar a un método protegido desde una clase principal.

+1

Gran explicación de por qué * debería * funcionar. (Y por qué la mayoría de las otras respuestas aquí son incorrectas). +1 – etherice

0

No es posible porque la lambda no es parte de la clase. Es lo mismo que realizar una función fuera de clase y llamarla en lugar de crear una lambda. Por supuesto, no tiene acceso a miembros privados.

9

El estándar (C++ 11, §5.1.2/3) establece que

El tipo de la lambda-expresión (que también es el tipo del objeto cierre) es un único, tipo de clase no sindical sin nombre - llamado tipo de cierre.

Ya que es un tipo de clase única que no es un friend de A, que no tiene acceso a los miembros privados A 's.

Lo que el compilador hace aquí es crear un tipo de clase que tenga los miembros apropiados para almacenar cualquier variable capturada, un operator() apropiado, que es exactamente lo que escribiría usted mismo si quisiera emular lambdas en C++ 03. Este tipo no tendría acceso a los miembros private, lo que podría hacer que sea más fácil visualizar por qué existe la limitación y por qué no hay una solución .

actualización con respecto a posibles soluciones:

sería mejor decir "no hay soluciones alternativas utilizando un lambda", porque en soluciones generales no existir aunque requieren que renunciar a la sintaxis lambda conveniente. Por ejemplo, usted podría:

  1. Escribe una clase local que captura de forma explícita this junto con cualesquiera otros locales que requiere (inspirado por el comentario de Björn Pollex a continuación).
  2. Escriba un método private en lugar de un lambda y páselo como devolución de llamada (por ejemplo, usando std::bind para mayor comodidad). Si desea capturar locales además de this, puede usar más std::bind en el sitio de llamadas para hacerlo.
+2

Sin embargo, funciona con una 'struct' local (http://ideone.com/AvsBE). Puedes explicar la diferencia? –

+0

@ BjörnPollex: Es una * local * 'struct', por lo que tiene acceso a los miembros privados de su tipo contenedor (citando el libro: * La clase local está en el alcance del alcance adjunto, y tiene el mismo acceso a nombres fuera de la función como lo hace la función adjunta. *). Sin embargo, podría decir que es una solución funcional. – Jon

+0

@Jon: Eso no explica por qué el compilador no simplemente hace que el tipo de cierre sea un amigo de la clase de la que es miembro.Si puede capturar "esto" implícitamente, actuando efectivamente como una función miembro, debería poder llegar a otros miembros privados. –

3

Solución:

typedef bool (A::*MemFn)(void) const; 

bool A::hasGrandChild() const { 
    MemFn f = &A::hasChild; 
    return any_of(childs_.begin(), childs_.end(), [=](A const &a) { 
      return (a.*f)(); 
    }); 
} 
+1

Espera. ¿Por qué diablos no harías 'any_of (begin, end, std :: mem_fn (& A :: hasChild))' que? Además de la penalización de rendimiento menor. – pmr

+0

@pmr - Esto es principalmente para ilustrar cómo acceder a las funciones de miembros privados en expresiones lambda. – Henrik

+0

@pmr: ¿penalización de rendimiento? No esperaría uno; es bastante obvio para un compilador razonable que 'f' no cambia. – MSalters

3

Puede capturar this explícitamente y convertirlo en un "miembro lambda" que tiene acceso a miembros privados.

Por ejemplo, consideremos el siguiente ejemplo:

#include <iostream> 
class A { 
private: 
    void f() { std::cout << "Private"; } 
public: 
    void g() { 
     [this] { 
      f(); 
      // doesn't need qualification 
     }(); 
    } 
}; 
class B { 
private: 
    void f() { std::cout << "Private"; } 
public: 
    void g() { [] { f(); }(); } // compiler error 
}; 
int main() { 
    A a; 
    a.g(); 
} 
Cuestiones relacionadas