Por que não consigo capturar isso por referência ('e isso') em lambda?

93

Eu entendo que a maneira correta de capturar this(para modificar as propriedades do objeto) em um lambda é a seguinte:

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

Mas estou curioso para saber a seguinte peculiaridade que vi:

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;
};

A estranheza que me confunde (e gostaria de ser respondida) é por que o seguinte funciona:

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

E por que não consigo capturar explicitamente thispor referência:

auto f = [&this] () { /* ... */ }; // a compiler error as seen above.
Anthony Sottile
fonte
6
Por que você quer ? Em termos de coisas para as quais uma referência a um ponteiro pode ser útil: thisnão pode ser alterado, não é grande o suficiente para tornar uma referência mais rápida ... e de qualquer forma , ele não existe de fato , então nenhuma vida real, o que significa que qualquer referência a ele estaria pendente por definição. thisé um prvalue, não um lvalue.
underscore_d

Respostas:

112

O motivo pelo qual [&this]não funciona é porque é um erro de sintaxe. Cada parâmetro separado por vírgula no lambda-introduceré um capture:

capture:
    identifier
    & identifier
    this

Você pode ver que isso &thisnão é permitido sintaticamente. O motivo pelo qual não é permitido é porque você nunca deseja capturar thispor referência, pois é um pequeno ponteiro const. Você só gostaria de passá-lo por valor - então, a linguagem simplesmente não suporta a captura thispor referência.

Para capturar thisexplicitamente, você pode usar [this]como o lambda-introducer.

O primeiro capturepode ser um capture-defaultque é:

capture-default:
    &
    =

Isso significa capturar automaticamente tudo o que eu uso, por referência ( &) ou por valor ( =) respectivamente - no entanto, o tratamento de thisé especial - em ambos os casos é capturado por valor pelas razões dadas anteriormente (mesmo com uma captura padrão de &, o que geralmente significa captura por referência).

5.1.2.7/8:

Para fins de pesquisa de nome (3.4), determinar o tipo e valor de this(9.3.2) e transformar expressões id referentes a membros de classe não estáticos em expressões de acesso de membros de classe usando (*this)(9.3.1), a instrução composta [OF O LAMBDA] é considerado no contexto da expressão lambda.

Portanto, o lambda atua como se fosse parte da função de membro envolvente ao usar nomes de membro (como em seu exemplo o uso do nome x), então ele irá gerar "usos implícitos" thisexatamente como faz uma função de membro.

Se uma captura lambda inclui um padrão de captura, isto é &, os identificadores na captura lambda não devem ser precedidos por &. Se uma captura lambda inclui um padrão de captura, isto é =, a captura lambda não deve conter thise cada identificador que contém deve ser precedido por &. Um identificador ou thisnão deve aparecer mais de uma vez em uma captura lambda.

Assim você pode usar [this], [&], [=]ou [&,this]como um lambda-introducerpara capturar othis ponteiro por valor.

No entanto [&this]e [=, this]estão mal formados. No último caso, o gcc avisa com perdão sobre [=,this]isso explicit by-copy capture of ‘this’ redundant with by-copy capture defaultao invés de erros.

Andrew Tomazos
fonte
3
@KonradRudolph: E se você quiser capturar algumas coisas por valor e outras por referência? Ou quer ser muito explícito com o que capta?
Xeo
2
@KonradRudolph: É um recurso de segurança. Você pode acidentalmente capturar nomes que não deseja.
Andrew Tomazos
8
@KonradRudolph: Construções em nível de bloco não copiam magicamente um ponteiro para os objetos que usam em um novo tipo anônimo invisível que pode então sobreviver ao escopo delimitador - simplesmente usando o nome do objeto em uma expressão. A captura de lambda é um negócio muito mais perigoso.
Andrew Tomazos
5
@KonradRudolph Eu diria "use [&]se você estiver fazendo algo como criar um bloco a ser passado para uma estrutura de controle", mas capture explicitamente se você estiver produzindo um lambda que será usado para propósitos menos simples. [&]é uma ideia horrível se o lambda vai sobreviver ao escopo atual. No entanto, muitos usos de lambdas são apenas maneiras de passar blocos para estruturas de controle, e o bloco não sobreviverá ao bloco que foi criado em escopo.
Yakk - Adam Nevraumont
2
@Ruslan: Não, thisé uma palavra-chave, thisnão é um identificador.
Andrew Tomazos
7

Porque o padrão não tem &this em 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. Para fins de captura lambda, uma expressão potencialmente faz referência a entidades locais da seguinte maneira:

    7.3 A esta expressão potencialmente faz referência a * this.

Portanto, o padrão garante thise *thisé válido e &thisé inválido. Além disso, capturar thissignifica capturar *this(que é um lvalue, o próprio objeto) por referência , em vez de capturar thisponteiro por valor !

陳 力
fonte
*thiscaptura o objeto por valor
sp2danny