Comment la macro lambda crée-t-elle un lambda?


20

J'ai trouvé ce morceau de code sur GitHub mais je ne l'ai pas bien compris:

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

Alors:

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

Que font les traits de soulignement à l'intérieur du #defineet comment renvoie-t-il un pointeur de fonction?


7
Avez-vous essayé simplement d'étendre la macro (par exemple avec gcc -E) pour voir ce qu'elle fait?
inutile

5
Veuillez regarder l'extension godbolt.org/z/C5TLWj Le résultat n'est pas facile à comprendre cependant
Eugene Sh.

2
Je suppose que vous savez sur la base des commentaires à l'endroit où vous avez obtenu ce code, mais cela dépend des extensions GCC pour les fonctions imbriquées.
Thomas Jager

4
@EugeneSh. Il s'agit d'initialiser un pointeur de fonction en utilisant les fonctions imbriquées de GCC. Le code d'origine est d' ici . Ce projet a été partagé sur Hacker News aujourd'hui.
Thomas Jager

4
@EugeneSh. C'est une combinaison de deux extensions GCC: fonctions imbriquées et instructions composées dans les expressions . La fonction imbriquée apparaît à l'intérieur de l'instruction composée.
interjay

Réponses:


10

En utilisant cette macro,

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

s'étend à:

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

Dans les accolades, cela utilise les fonctions imbriquées de GCC pour créer une fonction qui effectue l'opération souhaitée. Dans le cadre interne, il a le nom_ .

Ensuite, comme indiqué par interjay, les expressions de déclaration de GCC sont utilisées. En effet, la fonction_ est affectée au pointeur max.

Si une telle macro n'est pas utilisée, cela pourrait être écrit différemment et utilisé comme:

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

Les trois méthodes peuvent être comparées dans cet exemple de code .


La macro ne fait rien car elle ne se compilera pas dans gcc
P__J__

@P__J__ Il compile ideone.com/T5FLXb
Eugene Sh.

@P__J__ J'ai ajouté un exemple à la fin de ma réponse qui montre également que cette macro est utilisée.
Thomas Jager

Pourquoi ne pouvez-vous pas faire à la max(4, -30);place de apply_binary_op(max, 4, -30);?
SS Anne

1
Les "instructions composées dans les expressions" sont appelées "expressions d'instructions". Les instructions qui ont une valeur qui peut (par exemple) être affectée à quelque chose.
Peter Cordes

7

Cela s'appelle une expression de déclaration et crée une "lambda" (ou fonction imbriquée ) et lui renvoie un pointeur. Il est spécifique à GNU C.

La macro se développe pour:

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

Le _à la fin est comme un return.

Le trait de soulignement est en fait le nom de la fonction qui est créée et "renvoyée". Il est utilisé parce que c'est un identifiant peu utilisé (pour une bonne raison; _c'est probablement l'identifiant le moins descriptif possible).

La raison pour laquelle l'expression d'instruction est utilisée _ne le sera donc pas après la fin de la portée de l'expression d'instruction.

Donc, en passant par la macro:

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

ret_typeest le type de retour de la "lambda". _est le nom de la fonction utilisée à l'intérieur, car il s'agit d'un nom d'identifiant peu commun._bodyse compose des arguments et du corps de la fonction. Le dernier _"renvoie" le "lambda".

Ce code se trouve sur Let's Destroy C (qui est un nom approprié). Vous ne devriez pas l'utiliser. Il fera fonctionner votre code uniquement sur les compilateurs qui prennent en charge les extensions GNU C. Au lieu de cela, écrivez simplement une fonction ou une macro.

Si vous utilisez beaucoup de constructions comme celle-ci ou si vous voulez plus de fonctionnalités, je vous suggère d'utiliser C ++. Avec C ++, vous pouvez faire quelque chose de similaire et avoir du code portable.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.