Captura de Lambda y parámetro con el mismo nombre: ¿quién sombrea al otro? (clang vs gcc)

125
auto foo = "You're using g++!";
auto compiler_detector = [foo](auto foo) { std::puts(foo); };
compiler_detector("You're using clang++!");
  • clang ++ 3.6.0 y versiones posteriores imprimen "¡Estás usando clang ++!" y advertir sobre la captura foo no utilizada.

  • g ++ 4.9.0 y versiones posteriores imprimen "¡Estás usando g ++!" y advertir sobre el parámetro foo no utilizado.

¿Qué compilador sigue con mayor precisión el estándar C ++ aquí?

ejemplo de wandbox

Vittorio Romeo
fuente
1
Pegar el código de wandbox aquí (parece que han olvidado el botón de compartir) hace que parezca que VS2015 (?) Está de acuerdo con el sonido metálico que dice la advertencia C4458: la declaración de 'foo' oculta al miembro de la clase .
nwp
12
Gran ejemplo ..
deviantfan
44
La lambda tiene un tipo con un operador de llamada de función de plantilla, por lo tanto, la lógica me haría decir que el parámetro debe sombrear la variable capturada como si estuviera en struct Lambda { template<typename T> void operator()(T foo) const { /* ... */ } private: decltype(outer_foo) foo{outer_foo}; }.
skypjack
2
@nwp VS está mal, los miembros de datos de la lambda no tienen nombre y, por lo tanto, no se pueden sombrear. El estándar dice que "el acceso a una entidad capturada se transforma para acceder al miembro de datos correspondiente", lo que nos deja en el cuadrado.
n. 'pronombres' m.
10
Espero que la versión de clang sea correcta: ¡estaría abriendo nuevos caminos si algo fuera de una función sombrea el parámetro de la función, en lugar de al revés!
MM

Respuestas:

65

Actualización: como lo prometió el presidente de Core en la cita inferior, el código ahora está mal formado :

Si un identificador en una sencilla captura aparece como el declarador-id de un parámetro de la lambda-declarador 's declaración-parámetro-cláusula , está mal formado el programa.


Hubo algunos problemas relacionados con la búsqueda de nombres en lambdas hace un tiempo. Fueron resueltos por N2927 :

La nueva redacción ya no se basa en la búsqueda para reasignar los usos de las entidades capturadas. Niega más claramente las interpretaciones de que la declaración compuesta de una lambda se procesa en dos pasadas o que cualquier nombre en esa declaración compuesta podría resolver a un miembro del tipo de cierre.

La búsqueda siempre se realiza en el contexto de la expresión lambda , nunca "después" de la transformación al cuerpo de la función miembro de un tipo de cierre. Ver [expr.prim.lambda] / 8 :

La lambda-expresión 's compuesto declaración produce el cuerpo de la función ([dcl.fct.def]) del operador llamada a la función, pero para fines de búsqueda de nombre, [...], el compuesto declaración se considera en el contexto de La expresión lambda . [ Ejemplo :

struct S1 {
  int x, y;
  int operator()(int);
  void f() {
    [=]()->int {
      return operator()(this->x+y);  // equivalent to: S1::operator()(this->x+(*this).y)
                                     // and this has type S1*
    }; 
  }
};

- ejemplo final ]

(El ejemplo también deja en claro que la búsqueda de alguna manera no considera el miembro de captura generado del tipo de cierre).

El nombre foono se (re) declara en la captura; se declara en el bloque que encierra la expresión lambda. El parámetro foose declara en un bloque que está anidado en ese bloque externo (consulte [basic.scope.block] / 2 , que también menciona explícitamente los parámetros lambda). El orden de búsqueda es claramente de bloques internos a externos . Por lo tanto, se debe seleccionar el parámetro, es decir, Clang es correcto.

Si hiciera la captura una captura inicial, es decir, en foo = ""lugar de foo, la respuesta no sería clara. Esto se debe a que la captura ahora induce una declaración cuyo "bloque" no se da. Le envié un mensaje al presidente central sobre esto, quien respondió

Este es el número 2211 (en breve aparecerá una nueva lista de problemas en el sitio open-std.org, desafortunadamente con solo marcadores de posición para varios problemas, de los cuales este es uno; estoy trabajando duro para llenar esos vacíos antes de Kona reunión a fin de mes). CWG discutió esto durante nuestra teleconferencia de enero, y la dirección es hacer que el programa esté mal formado si un nombre de captura también es un nombre de parámetro.

Columbo
fuente
No hay nada que pueda romper aquí :) Una captura simple no declara nada, por lo que el resultado correcto de la búsqueda de nombres es bastante obvio (por cierto, GCC lo hace bien si usa un valor predeterminado de captura en lugar de una captura explícita). init-capture s son algo más complicados.
TC
1
@TC Estoy de acuerdo. Presenté un problema central, pero aparentemente esto ya se ha discutido, vea la respuesta editada.
Columbo
6

Estoy tratando de reunir algunos comentarios a la pregunta para darle una respuesta significativa.
En primer lugar, tenga en cuenta que:

  • Los miembros de datos no estáticos se declaran para el lambda para cada variable capturada por copia
  • En el caso específico, la lambda tiene un tipo de cierre que tiene un operador público de llamada de función de plantilla en línea que acepta un parámetro llamado foo

Por lo tanto, la lógica me haría decir a primera vista que el parámetro debería sombrear la variable capturada como si estuviera en:

struct Lambda {
    template<typename T> void operator()(T foo) const { /* ... */ }
    private: decltype(outer_foo) foo{outer_foo};
};

De todos modos, @nm notó correctamente que los miembros de datos no estáticos declarados para variables capturadas con copia en realidad no tienen nombre. Dicho esto, todavía se accede al miembro de datos sin nombre mediante un identificador (es decir foo). Por lo tanto, el nombre del parámetro del operador de llamada de función aún debería (déjame decir) sombrear ese identificador .
Como señaló correctamente @nm en los comentarios a la pregunta:

la entidad original capturada [...] debería sombrearse normalmente de acuerdo con las reglas de alcance

Por eso, diría que el sonido metálico es correcto.

skypjack
fuente
Como se explicó anteriormente, la búsqueda en este contexto nunca se realiza como si estuviéramos en el tipo de cierre transformado.
Columbo
@Columbo Estoy agregando una línea que me perdí incluso si estaba claro por el razonamiento, es que el sonido metálico es correcto. La parte divertida es que encontré [expr.prim.lambda] / 8 al intentar dar una respuesta, pero no he podido usarla correctamente como lo hizo. Es por eso que cada vez es un placer leer sus respuestas. ;-)
skypjack