Captura Lambda como referência const?

166

É possível capturar por referência const em uma expressão lambda?

Quero que a tarefa marcada abaixo falhe, por exemplo:

#include <cstdlib>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";

    for_each( &strings[0], &strings[num_strings], [&best_string](const string& s)
      {
        best_string = s; // this should fail
      }
    );
    return 0;
}

Atualização: Como essa é uma pergunta antiga, pode ser bom atualizá-la se houver recursos no C ++ 14 para ajudar nisso. As extensões no C ++ 14 nos permitem capturar um objeto não const por referência const? ( Agosto de 2015 )

John Dibling
fonte
seu lambda não deve se parecer com [&, &best_string](string const s) { ...}:?
Erjot
3
captura realmente inconsistente. "const &" pode ser muito útil quando você tem um objeto const grande que deve ser acessado, mas não modificado, na função lambda
sergtk 30/03/12
olhando o código. você pode usar um lambda de dois parâmetros e vincular o segundo como uma referência const. vem com um custo embora.
11283 Alex
1
Isso não é possível no C ++ 11, ao que parece. Mas talvez possamos atualizar esta questão para o C ++ 14 - existem extensões que permitem isso? O C ++ 14 lambda generalizado captura?
Aaron McDaid

Respostas:

127

const não está na gramática para capturas a partir de n3092:

capture:
  identifier
  & identifier
  this

O texto menciona apenas captura por cópia e captura por referência e não menciona nenhum tipo de constância.

Parece uma supervisão para mim, mas não acompanhei o processo de padronização de muito perto.

Steve M
fonte
47
Acabei de rastrear um bug de volta a uma variável sendo modificada a partir da captura que era mutável, mas deveria ter sido const. Ou, mais corretamente, se a variável de captura fosse const, o compilador aplicaria o comportamento correto no programador. Seria bom se a sintaxe fosse suportada [&mutableVar, const &constVar].
8373 Sean
Parece que isso deve ser possível com o C ++ 14, mas não consigo fazê-lo funcionar. Alguma sugestão?
Aaron McDaid
37
A consistência é herdada da variável capturada. Então, se você deseja capturar acomo const, declarar const auto &b = a;antes do lambda e capturab
StenSoft
7
@StenSoft Bleargh. Exceto, aparentemente, isso não se aplica ao capturar uma variável de membro por referência: [&foo = this->foo]dentro de uma constfunção, aparece um erro informando que a própria captura descarta qualificadores. Isso poderia ser um bug no GCC 5.1, suponho.
Kyle Strand
119

No usando static_cast/ const_cast:

[&best_string = static_cast<const std::string&>(best_string)](const string& s)
{
    best_string = s; // fails
};

DEMO


No usando std::as_const:

[&best_string = std::as_const(best_string)](const string& s)
{
    best_string = s; // fails
};

DEMO 2

Piotr Skotnicki
fonte
Além disso, talvez isso deva ser editado na resposta aceita? De qualquer forma, deve haver uma boa resposta que cubra tanto o c ++ 11 quanto o c ++ 14. Embora, eu acho que se possa argumentar que o c ++ 14 será bom o suficiente para todos nos próximos anos #
Aaron McDaid
12
@AaronMcDaid const_castpode incondicionalmente mudar um objeto volátil para um objeto const (quando solicitado a elenco para const), assim, para a adição de restrições prefirostatic_cast
Piotr Skotnicki
1
@PiotrSkotnicki por outro lado, static_casta referência const pode silenciosamente criar um temporária se você não obter o tipo exatamente certo
MM
24
@MM &basic_string = std::as_const(best_string)deve resolver todos os problemas
Piotr Skotnicki
14
@PiotrSkotnicki Exceto o problema de ser uma maneira hedionda de escrever algo que deve ser tão simples quanto const& best_string.
Kyle Strand
12

Eu acho que a parte de captura não deve especificar const, como a captura significa, ela só precisa de uma maneira de acessar a variável de escopo externo.

O especificador é melhor especificado no escopo externo.

const string better_string = "XXX";
[&better_string](string s) {
    better_string = s;    // error: read-only area.
}

A função lambda é const (não pode alterar o valor em seu escopo); portanto, quando você captura uma variável por valor, a variável não pode ser alterada, mas a referência não está no escopo lambda.

zhb
fonte
1
@Amarnath Balasubramani: É apenas a minha opinião, acho que não há necessidade de especificar uma referência const na parte de captura lambda, por que deveria haver uma variável const aqui e não const em outro local (se possível, será propenso a erros) ) feliz em ver sua resposta de qualquer maneira.
Zhb
2
Se você precisar modificar better_stringdentro do escopo que contém, esta solução não funcionará. O caso de uso para capturar como const-ref é quando a variável precisa ser mutável no escopo que o contém, mas não dentro do lambda.
Jonathan Sharman
@ JonathanSharman, não custa nada criar uma referência const para uma variável, para que você possa fazer uma const string &c_better_string = better_string;e passar feliz para a lambda:[&c_better_string]
Steed
@Steed O problema é que você está introduzindo um nome de variável extra no escopo circundante. Penso que a solução de Piotr Skotnicki acima é a mais limpa, pois atinge a correção constante, mantendo os escopos variáveis ​​mínimos.
Jonathan Sharman
@ JonathanSharman, aqui entramos na terra das opiniões - o que é mais bonito, ou mais limpo, ou o que for. O que quero dizer é que ambas as soluções são adequadas para a tarefa.
Steed
8

Acho que se você não estiver usando a variável como parâmetro do functor, deverá usar o nível de acesso da função atual. Se você acha que não deveria, então separe seu lambda dessa função, ela não faz parte dela.

De qualquer forma, você pode facilmente obter o mesmo que deseja usando outra referência const:

#include <cstdlib>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";
    const string& string_processed = best_string;

    for_each( &strings[0], &strings[num_strings], [&string_processed]  (const string& s)  -> void 
    {
        string_processed = s;    // this should fail
    }
    );
    return 0;
}

Mas é o mesmo que assumir que seu lambda precisa ser isolado da função atual, tornando-o não lambda.

Klaim
fonte
1
A cláusula de captura ainda menciona best_stringapenas. Além disso, o GCC 4.5 "rejeita com êxito" o código como pretendido.
sellibitze
Sim, isso me daria os resultados que eu estava tentando alcançar em nível técnico. Em última análise, no entanto, a resposta à minha pergunta original parece ser "não".
John Dibling 23/09/10
Por que isso tornaria um "não lambda"?
Porque a natureza de uma lambda é que ela depende do contexto. Se você não precisa de um contexto específico, é apenas uma maneira rápida de criar um functor. Se o functor deve ser independente do contexto, torne-o um functor real.
Klaim 23/09/10
3
"Se o functor deve ser independente do contexto, torne-o um functor real" ... e dar um beijo de despedida possível?
Andrew Lazarus
5

Eu acho que você tem três opções diferentes:

  • não use referência const, mas use uma captura de cópia
  • ignorar o fato de que é modificável
  • use std :: bind para vincular um argumento de uma função binária que tenha uma referência const.

usando uma cópia

A parte interessante sobre lambdas com capturas de cópias é que elas são apenas de leitura e, portanto, fazem exatamente o que você deseja.

int main() {
  int a = 5;
  [a](){ a = 7; }(); // Compiler error!
}

usando std :: bind

std::bindreduz a aridade de uma função. Observe, no entanto, que isso pode / levará a uma chamada de função indireta através de um ponteiro de função.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}
Alex
fonte
1
Exceto as alterações na variável no escopo que contém, não serão refletidas no lambda. Não é uma referência, é apenas uma variável que não deve ser transferida, porque a transferência não significaria o que pareceria significar.
Grault
4

Existe um caminho mais curto.

Observe que não há e comercial antes de "best_string".

Será do tipo "const std :: reference_wrapper << T >>".

[best_string = cref(best_string)](const string& s)
{
    best_string = s; // fails
};

http://coliru.stacked-crooked.com/a/0e54d6f9441e6867

Sergey Palitsin
fonte
0

Use clang ou aguarde até que esse bug do gcc seja corrigido: bug 70385: A captura do Lambda por referência da referência const falha [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]

user1448926
fonte
1
Embora esse link possa responder à pergunta, é melhor incluir aqui as partes essenciais da resposta e fornecer o link para referência. As respostas somente para links podem se tornar inválidas se a página vinculada for alterada. ”
Div
Ok, editei minha resposta para adicionar a descrição do bug do gcc aqui.
user1448926
Esta é uma resposta bastante indireta à pergunta, se houver. O bug é sobre como um compilador falha ao capturar algo const, portanto, talvez por que alguma maneira de resolver ou solucionar o problema na questão possa não funcionar com o gcc.
Stein
0

O uso de uma const simplesmente faz com que o e comercial do algoritmo defina a string como seu valor original. Em outras palavras, o lambda não se definirá realmente como parâmetro da função, embora o escopo ao redor tenha uma variável extra ... Sem defini-la no entanto, ela não definiria a string como o típico [&, & best_string] (string const s) Portanto , é mais provável que seja melhor deixarmos assim, tentando capturar a referência.

Saith
fonte
É uma pergunta muito antiga: sua resposta está sem contexto relacionado a qual versão do C ++ você está se referindo. Forneça este conteúdo.
ZF007