¿Por qué no puedo capturar esto por referencia ('& this') en lambda?

91

Entiendo que la forma correcta de capturar this(modificar las propiedades del objeto) en una lambda es la siguiente:

auto f = [this] () { /* ... */ };

Pero tengo curiosidad por la siguiente peculiaridad que he visto:

class C {
    public:
        void foo() {
            // auto f = [] () { // this not captured
            auto f = [&] () { // why does this work?
            // auto f = [&this] () { // Expected ',' before 'this'
            // auto f = [this] () { // works as expected
                x = 5;
            };
            f();
        }

    private:
        int x;
};

La rareza que me confunde (y me gustaría recibir una respuesta) es por qué funciona lo siguiente:

auto f = [&] () { /* ... */ }; // capture everything by reference

Y por qué no puedo capturar explícitamente thispor referencia:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.
Anthony Sottile
fuente
6
¿Por qué querrías hacerlo? En términos de cosas para las que una referencia a un puntero podría ser útil: thisno se puede cambiar, no es lo suficientemente grande para hacer una referencia más rápido ... y de todos modos , en realidad no existe , por lo que ha ninguna vida real, lo que significa que cualquier referencia a ella estaría pendiente por definición. thises un valor pr, no un valor l.
underscore_d

Respuestas:

111

La razón [&this]no funciona es porque es un error de sintaxis. Cada parámetro separado por comas en el lambda-introduceres un capture:

capture:
    identifier
    & identifier
    this

Puede ver que eso &thisno está permitido sintácticamente. La razón por la que no está permitido es porque nunca querría capturar thispor referencia, ya que es un pequeño puntero constante. Solo querrá pasarlo por valor, por lo que el lenguaje simplemente no admite la captura thispor referencia.

Para capturar thisexplícitamente, puede usar [this]como lambda-introducer.

El primero capturepuede ser un capture-defaultque es:

capture-default:
    &
    =

Esto significa capturar automáticamente todo lo que uso, por referencia ( &) o por valor ( =) respectivamente, sin embargo, el tratamiento de thises especial, en ambos casos se captura por valor por las razones dadas anteriormente (incluso con una captura predeterminada de &, que generalmente significa capturar por referencia).

5.1.2.7/8:

Para propósitos de búsqueda de nombre (3.4), determinar el tipo y valor de this(9.3.2) y transformar expresiones id que se refieren a miembros de clase no estáticos en expresiones de acceso a miembros de clase usando (*this)(9.3.1), la declaración compuesta [OF THE LAMBDA] se considera en el contexto de la expresión lambda.

Entonces, lambda actúa como si fuera parte de la función miembro adjunta cuando se usan nombres de miembros (como en su ejemplo, el uso del nombre x), por lo que generará "usos implícitos" de la thismisma manera que lo hace una función miembro.

Si una captura lambda incluye una captura predeterminada &, es decir , los identificadores en la captura lambda no deben ir precedidos de &. Si una captura lambda incluye un valor predeterminado de captura =, es decir , la captura lambda no contendrá thisy cada identificador que contenga estará precedido por &. Un identificador o thisno aparecerá más de una vez en una captura lambda.

Así que usted puede utilizar [this], [&], [=]o [&,this]como lambda-introducerpara capturar el thispuntero por valor.

Sin embargo [&this]y [=, this]están mal formados. En el último caso, gcc advierte con perdón por [=,this]eso en explicit by-copy capture of ‘this’ redundant with by-copy capture defaultlugar de errores.

Andrés Tomazos
fuente
3
@KonradRudolph: ¿Qué pasa si quieres capturar algunas cosas por valor y otras por referencia? ¿O quieres ser muy explícito con lo que capturas?
Xeo
2
@KonradRudolph: Es una característica de seguridad. Puede capturar accidentalmente nombres que no tiene la intención de hacerlo.
Andrew Tomazos
8
@KonradRudolph: Las construcciones a nivel de bloque no copian mágicamente un puntero a los objetos que usan en un nuevo tipo anónimo invisible que luego puede sobrevivir al alcance adjunto, simplemente usando el nombre del objeto en una expresión. La captura de lambda es un negocio mucho más peligroso.
Andrew Tomazos
5
@KonradRudolph Yo diría "utilícelo [&]si está haciendo algo como crear un bloque para pasarlo a una estructura de control", pero capture explícitamente si está produciendo una lambda que se usará para propósitos menos simples. [&]es una idea horrible si el lambda va a sobrevivir al alcance actual. Sin embargo, muchos usos de lambdas son solo formas de pasar bloques a estructuras de control, y el bloque no sobrevivirá al bloque que se creó en el alcance.
Yakk - Adam Nevraumont
2
@Ruslan: No, thises una palabra clave, thisno es un identificador.
Andrew Tomazos
6

Porque estándar no tiene &thisen las listas de Capturas:

N4713 8.4.5.2 Capturas:

lambda-capture:
    capture-default
    capture-list
    capture-default, capture-list

capture-default:
    &
    =
capture-list:
    capture...opt
    capture-list, capture...opt
capture:
    simple-capture
    init-capture
simple-capture:
    identifier
    &identifier
    this
    * this
init-capture:
    identifier initializer
    &identifier initializer
  1. A los efectos de la captura lambda, una expresión potencialmente hace referencia a entidades locales de la siguiente manera:

    7.3 A esta expresión potencialmente hace referencia a * esto.

Entonces, estándar garantiza thisy *thises válido y &thisno es válido. Además, capturar thissignifica capturar *this(que es un valor, el objeto en sí) por referencia , en lugar de capturar el thispuntero por valor .

陳 力
fuente
*thiscaptura el objeto por valor
sp2danny