¿Cómo crea la macro lambda una lambda?


20

Encontré este fragmento de código en GitHub pero no lo entendí del todo:

#define lambda(ret_type, _body) ({ ret_type _ _body _; })

Entonces:

int (*max)(int, int) = lambda(int,
                             (int x, int y) {
                                 return x > y ? x : y;
                             });

int max_value = max(1, 2);
// max_value is 2

¿Qué están haciendo los guiones bajos dentro #definey cómo devuelve un puntero de función?


77
¿Has intentado expandir la macro (por ejemplo, con gcc -E) para ver qué hace?
Inútil

55
Mire la expansión godbolt.org/z/C5TLWj Sin embargo, el resultado no es fácil de entender
Eugene Sh.

2
Supongo que lo sabe según los comentarios en el lugar donde obtuvo este código, pero esto depende de las extensiones GCC para funciones anidadas.
Thomas Jager

44
@EugeneSh. Inicializa un puntero de función utilizando las funciones anidadas de GCC. El código original es de aquí . Este proyecto fue compartido en Hacker News hoy.
Thomas Jager

44
@EugeneSh. Es una combinación de dos extensiones de GCC: funciones anidadas y declaraciones compuestas en expresiones . La función anidada aparece dentro de la declaración compuesta.
Interjay

Respuestas:


10

Usando esta macro,

int (*max)(int, int) = lambda(int,
                             (int x, int y) {
                                 return x > y ? x : y;
                             });

se expande a:

int (*max)(int, int) = ({
    int _ (int x, int y) { return x > y ? x : y; }
    _;
});

En las llaves, esto usa GCC funciones anidadas para crear una función que realice la operación deseada. Dentro del alcance interno, tiene el nombre _.

Luego, como lo señaló Interjay, las expresiones de declaración de GCC se utilizan las . Efectivamente, la función _se asigna al puntero max.

Si no se usa una macro de este tipo, esto podría escribirse de manera diferente y usarse como:

int val1 = 4;
int val2 = -30;

int perform_operation(int (*op)(int, int)) {
    int new_val = op(val1, val2);
    val1 = val2;
    val2 = new_val;
    return new_val;
}

int enclosing_function (void) {
    // Create max "lambda"
    int (*max)(int, int);
    {
        // Curly braces limit the scope of _
        int _ (int x, int y) { return x > y ? x : y; }
        max = _;
    }

    return perform_operation(max);
}

Los tres métodos se pueden comparar en este ejemplo de código .


La macro no hace nada ya que no se compilará en gcc
P__J__

@P__J__ Compila ideone.com/T5FLXb
Eugene Sh.

@P__J__ He ​​agregado un ejemplo al final de mi respuesta que también muestra esta macro en uso.
Thomas Jager

¿Por qué no puedes hacer en max(4, -30);lugar de apply_binary_op(max, 4, -30);?
SS Anne

1
Las "declaraciones compuestas en expresiones" se denominan "expresiones de declaración". Declaraciones que tienen un valor que puede (p. Ej.) Asignarse a algo.
Peter Cordes

7

Esto se llama una expresión de instrucción y crea una "lambda" (o función anidada ) y le devuelve un puntero. Es GNU C específico.

La macro se expande a:

int (*max)(int, int) = ({ int _ (int x, int y) { return x > y ? x : y; } _; })

Al _final es como a return.

El guión bajo es en realidad el nombre de la función que se crea y "devuelve". Se usa porque es un identificador de uso poco frecuente (por una buena razón; _posiblemente sea el identificador menos descriptivo posible).

La razón por la que se usa la expresión de la declaración es _que no se definirá después de que salga el alcance de la expresión de la declaración.

Entonces, pasando por la macro:

#define lambda(ret_type, _body) ({ ret_type _ _body _; })

ret_typees el tipo de retorno de la "lambda". _es el nombre de la función utilizada en su interior porque es un nombre de identificador poco común._bodyconsiste en los argumentos y el cuerpo de la función. El final _"devuelve" la "lambda".

Este código se encuentra en Let's Destroy C (que es un nombre apropiado). No deberías usarlo. Hará que su código funcione solo en compiladores que admitan extensiones GNU C. En cambio, solo escribe una función o macro.

Si usa muchas construcciones como esta o desea más funciones, sugiero usar C ++. Con C ++ puedes hacer algo similar a esto y tener un código portátil.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.