web-dev-qa-db-fra.com

Déclaration directe de lambdas en C ++

En C++, il est possible de séparer la déclaration et la définition des fonctions. Par exemple, il est tout à fait normal de déclarer une fonction:

int Foo(int x);

dans Foo.h et l'implémenter dans Foo.cpp. Est-il possible de faire quelque chose de similaire avec des lambdas? Par exemple, définissez un

std::function<int(int)> bar;

dans bar.h et l'implémenter dans bar.cpp comme:

std::function<int(int)> bar = [](int n)
{
    if (n >= 5) 
        return n;
    return n*(n + 1);
};

Disclaimer: J'ai de l'expérience avec les lambdas en C #, mais je ne les ai pas beaucoup utilisés en C++.

34
MxNx

Vous ne pouvez pas séparer la déclaration et la définition de lambdas , ni la déclarer en avant. Son type est un type de fermeture sans nom unique qui est déclaré avec l'expression lambda. Mais vous pouvez le faire avec des objets std :: function , qui sont conçus pour pouvoir stocker n'importe quelle cible appelable, y compris les lambdas.

Comme votre exemple de code a montré que vous utilisiez std::function, Notez simplement que dans ce cas bar est une variable globale et que vous devez utiliser extern dans le fichier d'en-tête pour en faire une déclaration (pas une définition).

// bar.h
extern std::function<int(int)> bar;     // declaration

et

// bar.cpp
std::function<int(int)> bar = [](int n) // definition
{
    if (n >= 5) return n;
    return n*(n + 1);
};

Notez à nouveau qu'il ne s'agit pas d'une déclaration et d'une définition distinctes de lambda; Il s'agit simplement d'une déclaration et d'une définition distinctes d'une variable globale bar de type std::function<int(int)>, qui est initialisée à partir d'une expression lambda.

41
songyuanyao

La déclaration directe n'est pas le terme correct car les lambdas en C++ sont des objets, pas des fonctions. Le code:

std::function<int(int)> bar;

déclare une variable et vous n'êtes pas obligé de l'affecter (ce type a la valeur par défaut "pointeur sur aucune fonction"). Vous pouvez même y compiler des appels ... par exemple le code:

#include <functional>
#include <iostream>

int main(int argc, const char *argv[]) {
    std::function<int(int)> bar;
    std::cout << bar(21) << "\n";
    return 0;
}

compilera proprement (mais se comportera bien sûr de façon folle lors de l'exécution).

Cela dit, vous pouvez affecter un lambda à une variable std::function Compatible et ajouter par exemple:

bar = [](int x){ return x*2; };

juste avant l'appel se traduira par un programme qui compile bien et génère comme sortie 42.

Quelques choses non évidentes qui peuvent surprendre à propos des lambdas en C++ (si vous connaissez d'autres langages qui ont ce concept) sont que

  • Chaque lambda [..](...){...} a un type incompatible différent, même si la signature est absolument identique. Par exemple, vous ne pouvez pas déclarer un paramètre de type lambda car la seule façon serait d'utiliser quelque chose comme decltype([] ...) mais il n'y aurait alors aucun moyen d'appeler la fonction comme n'importe quelle autre forme []... À un le site d'appel serait incompatible. Ceci est résolu par std::function Donc si vous devez passer des lambdas ou les stocker dans des conteneurs, vous devez utiliser std::function.

  • Les lambdas peuvent capturer les locaux par valeur (mais ils sont const sauf si vous déclarez le lambda mutable) ou par référence (mais garantir la durée de vie de l'objet référencé ne sera pas plus courte que la durée de vie du lambda dépend du programmeur). C++ n'a pas de garbage collector et c'est quelque chose de nécessaire pour résoudre correctement le problème "funarg ascendant" (vous pouvez contourner en capturant des pointeurs intelligents, mais vous devez faire attention aux boucles de référence pour éviter les fuites).

  • Différemment des autres langues, les lambdas peuvent être copiés et lorsque vous les copiez, vous prenez un instantané de leurs variables internes par valeur capturées. Cela peut être très surprenant pour un état mutable et c'est à mon avis la raison pour laquelle les valeurs par valeur capturées sont const par défaut.

Un moyen de rationaliser et de se souvenir de nombreux détails sur les lambdas est ce code comme:

std::function<int(int)> timesK(int k) {
    return [k](int x){ return x*k; };
}

est fondamentalement comme

std::function<int(int)> timesK(int k) {
    struct __Lambda6502 {
        int k;
        __Lambda6502(int k) : k(k) {}
        int operator()(int x) {
            return x * k;
        }
    };
    return __Lambda6502(k);
}

avec une subtile différence que même les références de capture lambda peuvent être copiées (normalement les classes contenant des références en tant que membres ne peuvent pas).

8
6502

À strictement parler, vous ne pouvez pas

Citant de référence cpp

L'expression lambda est une expression de valeur dont la valeur est (jusqu'à C++ 17) dont l'objet de résultat est (depuis C++ 17) un objet temporaire sans nom de type de classe non agrégé non union non nommé unique, appelé type de fermeture, qui est déclaré (aux fins de l'ADL) dans la plus petite étendue de bloc, portée de classe ou étendue d'espace de noms qui contient l'expression lambda

Le lambda est donc un objet temporaire sans nom . Vous pouvez lier le lambda à un objet de valeur l (par exemple, std::function) et par des règles régulières sur la déclaration des variables, vous pouvez séparer la déclaration et la définition.

8
bashrc