¿Captura lambda como referencia constante?

166

¿Es posible capturar por referencia constante en una expresión lambda?

Quiero que la tarea marcada a continuación falle, por ejemplo:

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

Actualización: como esta es una pregunta antigua, podría ser bueno actualizarla si hay recursos en C ++ 14 para ayudar con esto. ¿Las extensiones en C ++ 14 nos permiten capturar un objeto no constante con referencia constante? ( Agosto 2015 )

John Dibling
fuente
¿No debería verse tu lambda [&, &best_string](string const s) { ...}:?
erjot
3
captura realmente inconsistente. "const &" puede ser muy útil cuando tiene un objeto const grande al que debe accederse pero no modificarse en la función lambda
sergtk
mirando el código podría usar un lambda de dos parámetros y vincular el segundo como referencia constante. Sin embargo, tiene un costo.
Alex
1
Parece que esto no es posible en C ++ 11. Pero quizás podamos actualizar esta pregunta para C ++ 14: ¿hay extensiones que lo permitan? ¿Las capturas lambda generalizadas de C ++ 14?
Aaron McDaid

Respuestas:

127

const no está en la gramática para capturas a partir de n3092:

capture:
  identifier
  & identifier
  this

El texto solo menciona la captura por copia y la captura por referencia y no menciona ningún tipo de constancia.

Me parece un descuido, pero no he seguido el proceso de estandarización muy de cerca.

Steve M
fuente
47
Acabo de rastrear un error hasta una variable modificada desde la captura que era mutable, pero debería haberlo sido const. O más correctamente, si la variable de captura fuera const, el compilador habría aplicado el comportamiento correcto en el programador. Sería bueno si la sintaxis fuera compatible [&mutableVar, const &constVar].
Sean
Parece que esto debería ser posible con C ++ 14, pero no puedo hacer que funcione. ¿Alguna sugerencia?
Aaron McDaid
37
La constidad se hereda de la variable capturada. Entonces, si desea capturar acomo const, declare const auto &b = a;antes de la lambda y captureb
StenSoft
77
@StenSoft Bleargh. Excepto al parecer esto no se aplica cuando la captura de un miembro variable por referencia: [&foo = this->foo]en el interior de una constfunción me da un error que indica que la captura en sí descarta calificadores. Sin embargo, esto podría ser un error en GCC 5.1, supongo.
Kyle Strand
119

En usando static_cast/ const_cast:

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

MANIFESTACIÓN


En utilizando std::as_const:

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

DEMO 2

Piotr Skotnicki
fuente
Además, ¿tal vez esto debería editarse en la respuesta aceptada? De cualquier manera, debería haber una buena respuesta que cubra tanto c ++ 11 como c ++ 14. Aunque, supongo que se podría argumentar que c ++ 14 será lo suficientemente bueno para todos en los próximos años
Aaron McDaid
12
@AaronMcDaid const_castpuede cambiar incondicionalmente un objeto volátil a un objeto constante (cuando se le pide que lo conststatic_cast
envíe
1
@PiotrSkotnicki, por otro lado, static_castconst referencia puede crear silenciosamente un temporal si no obtuvo el tipo exactamente correcto
MM
24
@MM &basic_string = std::as_const(best_string)debería resolver todos los problemas
Piotr Skotnicki
14
@PiotrSkotnicki Excepto el problema de que es una forma horrible de escribir algo que debería ser tan simple como const& best_string.
Kyle Strand
12

Creo que la parte de captura no debería especificar const, ya que la captura significa, solo necesita una forma de acceder a la variable de alcance externo.

El especificador se especifica mejor en el alcance externo.

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

La función lambda es constante (no puede cambiar el valor en su alcance), por lo que cuando captura una variable por valor, la variable no se puede cambiar, pero la referencia no está en el alcance lambda.

zhb
fuente
1
@Amarnath Balasubramani: Es solo mi opinión, creo que no es necesario especificar una referencia constante en la parte de captura lambda, ¿por qué debería haber una variable constante aquí y no constante en otro lugar (si es posible, será propensa a errores ) feliz de ver tu respuesta de todos modos.
zhb
2
Si necesita modificar better_stringdentro del alcance que contiene, entonces esta solución no funcionará. El caso de uso para capturar como const-ref es cuando la variable debe ser mutable en el ámbito de contención pero no dentro del lambda.
Jonathan Sharman
@JonathanSharman, no le cuesta nada crear una referencia constante a una variable, por lo que puede hacerla const string &c_better_string = better_string;y pasarla felizmente a la lambda:[&c_better_string]
Steed
@Steed El problema con eso es que está introduciendo un nombre de variable adicional en el alcance circundante. Creo que la solución anterior de Piotr Skotnicki es la más limpia, ya que logra una corrección constante y mantiene mínimos los alcances variables.
Jonathan Sharman el
@ JonathanSharman, aquí entramos en la tierra de las opiniones: ¿qué es lo más bonito, lo más limpio o lo que sea? Mi punto es que ambas soluciones son adecuadas para la tarea.
Steed
8

Supongo que si no estás usando la variable como parámetro del functor, entonces deberías usar el nivel de acceso de la función actual. Si crees que no deberías, separa tu lambda de esta función, no es parte de ella.

De todos modos, puede lograr fácilmente lo mismo que desea utilizando otra referencia constante:

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

Pero eso es lo mismo que asumir que su lambda tiene que estar aislada de la función actual, convirtiéndola en no lambda.

Klaim
fuente
1
La cláusula de captura todavía menciona best_stringsolo. Aparte de eso, GCC 4.5 "rechaza con éxito" el código como se pretendía.
sellibitze
Sí, esto me daría los resultados que estaba tratando de lograr a nivel técnico. Sin embargo, en última instancia, la respuesta a mi pregunta original parece ser "no".
John Dibling
¿Por qué eso lo convertiría en un "no lambda"?
Porque la naturaleza de una lambda es que depende del contexto. Si no necesita un contexto específico, entonces es solo una forma rápida de crear un functor. Si el functor debe ser independiente del contexto, conviértalo en un verdadero functor.
Klaim
3
"Si el functor debe ser independiente del contexto, conviértalo en un verdadero functor" ... ¿y un beso posible para despedirse?
Andrew Lazarus
5

Creo que tienes tres opciones diferentes:

  • no use referencia constante, pero use una captura de copia
  • ignorar el hecho de que es modificable
  • use std :: bind para vincular un argumento de una función binaria que tiene una referencia constante.

usando una copia

La parte interesante de las lambdas con capturas de copia es que en realidad son de solo lectura y, por lo tanto, hacen exactamente lo que usted desea.

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

usando std :: bind

std::bindReduce la aridad de una función. Sin embargo, tenga en cuenta que esto podría / conducirá a una llamada de función indirecta mediante un puntero de función.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}
Alex
fuente
1
Excepto que los cambios en la variable en el alcance que contiene no se reflejarán en el lambda. No es una referencia, es solo una variable que no debe reasignarse porque la reasignación no significaría lo que parece significar.
Grault
4

Hay un camino más corto.

Tenga en cuenta que no hay ampersand antes de "best_string".

Será del 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
fuente
0

Utilice clang o espere hasta que se solucione este error de gcc: error 70385: falla la captura de Lambda por referencia de referencia constante [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]

usuario1448926
fuente
1
Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si la página vinculada cambia ”.
Div
Ok, edité mi respuesta para agregar la descripción del error gcc aquí.
user1448926
Esta es una respuesta indirecta a la pregunta, si la hay. El error se trata de cómo falla un compilador al capturar algo constante, así que quizás por qué alguna forma de abordar o solucionar el problema en la pregunta podría no funcionar con gcc.
Stein
0

El uso de una constante simplemente tendrá el algoritmo ampers y establecerá la cadena en su valor original. En otras palabras, el lambda realmente no se definirá como parámetro de la función, aunque el alcance circundante tendrá una variable adicional ... Sin definirlo sin embargo, no definiría la cadena como la típica [&, & best_string] (cadena const s) Por lo tanto , lo más probable es que sea mejor si la dejamos así, tratando de capturar la referencia.

Dice
fuente
Es una pregunta muy antigua: su respuesta carece de contexto relacionado con la versión de C ++ a la que se refiere. Por favor proporcione este contenido.
ZF007