¿Cómo funciona lambda genérico en C ++ 14?

114

¿Cómo funciona lambda genérico ( autopalabra clave como tipo de argumento) en el estándar C ++ 14?

¿Se basa en plantillas C ++ donde para cada tipo de argumento diferente, el compilador genera una nueva función con el mismo cuerpo pero tipos reemplazados (polimorfismo en tiempo de compilación) o es más similar a los genéricos de Java (borrado de tipos)?

Ejemplo de código:

auto glambda = [](auto a) { return a; };
sasha.sochka
fuente
6
Se
corrigió

Respuestas:

130

Las lambdas genéricas se introdujeron en C++14.

Simplemente, el tipo de cierre definido por la expresión lambda tendrá un operador de llamada con plantilla en lugar del operador normal de llamada sin plantilla de C++11lambdas (por supuesto, cuando autoaparezca al menos una vez en la lista de parámetros).

Entonces tu ejemplo:

auto glambda = [] (auto a) { return a; };

Hará glambdauna instancia de este tipo:

class /* unnamed */
{
public:
    template<typename T>
    T operator () (T a) const { return a; }
};

El párrafo 5.1.2 / 5 del Borrador estándar de C ++ 14 n3690 especifica cómo se define el operador de llamada del tipo de cierre de una expresión lambda determinada:

El tipo de cierre para una expresión lambda no genérica tiene un operador de llamada de función en línea pública (13.5.4) cuyos parámetros y tipo de retorno se describen mediante la cláusula de declaración de parámetros y el tipo de retorno final de la expresión lambda, respectivamente. Para una lambda genérica, el tipo de cierre tiene una plantilla de miembro de operador de llamada de función en línea pública (14.5.2) cuya lista de parámetros de plantilla consta de un parámetro de plantilla de tipo inventado para cada aparición de auto en la cláusula de declaración de parámetros de lambda, En orden de aparicion. El parámetro de plantilla de tipo inventado es un paquete de parámetros si la declaración de parámetro correspondiente declara un paquete de parámetros de función (8.3.5). El tipo de retorno y los parámetros de función de la plantilla de operador de llamada de función se derivan del tipo de retorno final y la cláusula de declaración de parámetros de la expresión lambda reemplazando cada aparición de auto en los especificadores de decl de la cláusula de declaración de parámetros con el nombre de el correspondiente parámetro de plantilla inventado.

Finalmente:

¿Es similar a las plantillas donde para cada tipo de argumento diferente, el compilador genera funciones con el mismo cuerpo pero tipos modificados o es más similar a los genéricos de Java?

Como explica el párrafo anterior, las lambdas genéricas son simplemente azúcar sintáctico para functores únicos y sin nombre con un operador de llamada con plantilla. Eso debería responder a su pregunta :)

Andy Prowl
fuente
7
Sin embargo, también permiten una clase definida localmente con un método de plantilla. Que es nuevo.
Yakk - Adam Nevraumont
2
@Yakk: ¿No se eliminó por completo la restricción para las plantillas de función local con C ++ 11?
Sebastian Mach
2
@phresnel: No, esa restricción no se ha levantado
Andy Prowl
1
@AndyProwl: Me doy cuenta de mi error. Lo que se levantó de hecho fue el uso de tipos locales como argumentos de plantilla (como en int main () { struct X {}; std::vector<X> x; })
Sebastian Mach
1
@phresnel: Correcto, eso ha cambiado de hecho
Andy Prowl
25

Desafortunadamente , no forman parte de C ++ 11 ( http://ideone.com/NsqYuq ):

auto glambda = [](auto a) { return a; };

int main() {}

Con g ++ 4.7:

prog.cpp:1:24: error: parameter declared auto
...

sin embargo , la forma en que podría implementarse en C ++ 14 según la propuesta de Portland para lambdas genéricas :

[](const& x, & y){ return x + y; }

Esto produciría en su mayor parte la creación habitual de una clase functora anónima, pero con la falta de tipos, el compilador emitiría un miembro con plantilla operator():

struct anonymous
{
    template <typename T, typename U>
    auto operator()(T const& x, U& y) const -> decltype(x+y)
    { return x + y; }
};

O según la propuesta más reciente Propuesta para expresiones lambda genéricas (polimórficas)

auto L = [](const auto& x, auto& y){ return x + y; };

--->

struct /* anonymous */
{
    template <typename T, typename U>
    auto operator()(const T& x, U& y) const // N3386 Return type deduction
    { return x + y; }
} L;

Entonces sí, para cada permutación de parámetros, surgiría una nueva instanciación, sin embargo, los miembros de ese functor aún serían compartidos (es decir, los argumentos capturados).

Sebastián Mach
fuente
6
Esa propuesta de permitir eliminar el especificador de tipo es absolutamente grotesca.
Lightness Races in Orbit
Entraron con g ++ - 4.9 . Necesitas suministrar -std=c++1y.
emsr
No me di cuenta de que ideone aún no tiene gcc-4.9 y C ++ 14.
emsr
pregunta: ¿ autotiene las mismas reglas de deducción que el auto clásico? Si nos referimos a la analogía de la plantilla, significaría que el auto no es automático, son las mismas reglas que la deducción del tipo de plantilla. Entonces la pregunta es: ¿la deducción por plantilla es equivalente a auto?
v.oddou
@ v.oddou: "Auto clásico" es bueno. Para mí, "classic auto" significa "Stack Variable", y una vez se usó en contraste con statico register:) De todos modos, sí, usar autoallí significa que, bajo el capó, se genera una plantilla normal. De hecho, una lambda será reemplazada internamente por un compilador por una clase functor, y un autoparámetro significa que template <T> ... (T ...)se emitirá.
Sebastian Mach
17

Es una característica propuesta de C ++ 14 (no en C ++ 11) similar (o incluso equivalente) a las plantillas. Por ejemplo, N3559 proporciona este ejemplo:

Por ejemplo, esta declaración genérica que contiene una expresión lambda:

auto L = [](const auto& x, auto& y){ return x + y; };

podría resultar en la creación de un tipo de cierre y un objeto que se comporte de manera similar a la estructura siguiente:

struct /* anonymous */
{
    template <typename T, typename U>
    auto operator()(const T& x, U& y) const // N3386 Return type deduction
    { return x + y; }
} L;
Cassio Neri
fuente