Como a macro lambda cria uma lambda?

20

Encontrei este código no GitHub, mas não o entendi:

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

Então:

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

O que os sublinhados estão fazendo dentro do #definee como ele retorna um ponteiro de função?

Kish
fonte
7
Você já tentou expandir a macro (por exemplo, com gcc -E) para ver o que ela faz?
Inútil
5
Por favor, observe a expansão godbolt.org/z/C5TLWj O resultado não é fácil de entender
Eugene Sh.
2
Suponho que você saiba com base nos comentários ao redor do local onde obteve esse código, mas isso depende das extensões do GCC para funções aninhadas.
Thomas Jager
4
@EugeneSh. Ele está inicializando um ponteiro de função usando as funções aninhadas do GCC. O código original é daqui . Este projeto foi compartilhado no Hacker News hoje.
Thomas Jager
4
@EugeneSh. É uma combinação de duas extensões do GCC: funções aninhadas e instruções compostas em expressões . A função aninhada aparece dentro da instrução composta.
interjay 30/01

Respostas:

10

Usando essa macro,

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

expande para:

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

Nos chavetas, isso usa as funções aninhadas do GCC para criar uma função que executa a operação desejada. No âmbito interno, que tem o nome _.

Então, como observado pelo interjay, os GCCs Expressões de declaração são usadas. Efetivamente, a função _é atribuída ao ponteiro max.

Se essa macro não estiver sendo usada, isso poderá ser escrito de forma diferente e usado 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);
}

Os três métodos podem ser comparados neste exemplo de código .

Thomas Jager
fonte
A macro não faz nada, pois não será compilada no gcc
P__J__ 30/01
@P__J__ Compila ideone.com/T5FLXb
Eugene Sh.
@P__J__ Adicionei um exemplo ao final da minha resposta que também mostra essa macro sendo usada.
Thomas Jager
Por que você não pode fazer em max(4, -30);vez de apply_binary_op(max, 4, -30);?
SS Anne
11
"Instruções compostas em expressões" são chamadas "expressões de instrução". Instruções que têm um valor que pode (por exemplo) ser atribuído a alguma coisa.
Peter Cordes
7

Isso é chamado de expressão de instrução e cria uma "lambda" (ou função aninhada ) e retorna um ponteiro para ela. É específico do GNU C.

A macro se expande para:

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

O _no final é como a return.

O sublinhado é realmente o nome da função criada e "retornada". É usado porque é um identificador pouco utilizado (por boas razões; _é possivelmente o identificador menos descritivo possível).

O motivo pelo qual a expressão de instrução é usada é que _não será definido após a saída do escopo da expressão de instrução.

Então, passando pela macro:

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

ret_typeé o tipo de retorno do "lambda". _é o nome da função usada nela porque é um nome de identificador incomum. _bodyconsiste nos argumentos e corpo da função. A trilha_ "retorna" o "lambda".

Este código é encontrado em Let's Destroy C (que é um nome apropriado). Você não deveria usá-lo. Isso fará com que seu código funcione apenas em compiladores que suportam extensões GNU C. Em vez disso, basta escrever uma função ou macro.

Se você usa muito essas construções ou deseja mais recursos, sugiro usar C ++. Com o C ++, você pode fazer algo semelhante a isso e ter código portátil.

SS Anne
fonte