Comment fonctionne lambda générique en C ++ 14?

114

Comment fonctionne lambda générique ( automot-clé comme type d'argument) dans la norme C ++ 14?

Est-il basé sur des modèles C ++ où pour chaque type d'argument différent, le compilateur génère une nouvelle fonction avec le même corps mais des types remplacés (polymorphisme à la compilation) ou est-il plus similaire aux génériques de Java (effacement de type)?

Exemple de code:

auto glambda = [](auto a) { return a; };
sasha.sochka
la source
6
Correction à C ++ 14, utilisé à l'origine C ++ 11 en question
sasha.sochka

Réponses:

130

Les lambdas génériques ont été introduits dans C++14.

Simplement, le type de fermeture défini par l'expression lambda aura un opérateur d'appel basé sur un modèle plutôt que l'opérateur d'appel normal et non modèle C++11des lambdas de s (bien sûr, lorsqu'il autoapparaît au moins une fois dans la liste de paramètres).

Donc votre exemple:

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

Fera glambdaune instance de ce type:

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

Le paragraphe 5.1.2 / 5 du projet de norme C ++ 14 n3690 spécifie comment l'opérateur d'appel du type de fermeture d'une expression lambda donnée est défini:

Le type de fermeture pour une expression lambda non générique a un opérateur d'appel de fonction public en ligne (13.5.4) dont les paramètres et le type de retour sont décrits respectivement par la clause de déclaration de paramètre et le type de retour de fin de l'expression lambda. Pour un lambda générique, le type de fermeture a un modèle de membre d'opérateur d'appel de fonction publique en ligne (14.5.2) dont la liste de paramètres de modèle consiste en un paramètre de modèle de type inventé pour chaque occurrence de auto dans la clause de déclaration de paramètre de lambda, par ordre d'apparition. Le paramètre-modèle de type inventé est un pack de paramètres si la déclaration de paramètre correspondante déclare un pack de paramètres de fonction (8.3.5). Le type de retour et les paramètres de fonction du modèle d'opérateur d'appel de fonction sont dérivés du type de retour de fin et de la clause de déclaration de paramètre de l'expression lambda en remplaçant chaque occurrence de auto dans les spécificateurs de déclin de la clause de déclaration de paramètre par le nom de le paramètre-modèle inventé correspondant.

Finalement:

Est-ce similaire aux modèles où, pour chaque type d'argument différent, le compilateur génère des fonctions avec le même corps mais des types modifiés ou est-ce plus similaire aux génériques de Java?

Comme l'explique le paragraphe ci-dessus, les lambdas génériques ne sont que du sucre syntaxique pour des foncteurs uniques et sans nom avec un opérateur d'appel basé sur un modèle. Cela devrait répondre à votre question :)

Andy Prowl
la source
7
Cependant, ils autorisent également une classe définie localement avec une méthode de modèle. Ce qui est nouveau.
Yakk - Adam Nevraumont
2
@Yakk: La restriction pour les modèles locaux de fonction n'a-t-elle pas déjà été supprimée avec C ++ 11?
Sebastian Mach
2
@phresnel: Non, cette restriction n'a pas été levée
Andy Prowl
1
@AndyProwl: Je réalise mon erreur. Ce qui a été levé en effet, c'était l'utilisation de types locaux comme arguments de modèle (comme dans int main () { struct X {}; std::vector<X> x; })
Sebastian Mach
1
@phresnel: Oui, cela a en effet changé
Andy Prowl
25

Malheureusement , ils ne font pas partie de C ++ 11 ( http://ideone.com/NsqYuq ):

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

int main() {}

Avec g ++ 4.7:

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

Cependant , la façon dont il pourrait être implémenté en C ++ 14 selon la proposition de Portland pour les lambdas génériques :

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

Cela donnerait pour l'essentiel la création habituelle d'une classe de foncteurs anonyme, mais avec le manque de types, le compilateur émettrait un membre basé sur un modèle operator():

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

Ou selon la nouvelle proposition Proposition d'expressions lambda génériques (polymorphes)

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;

Alors oui, pour chaque permutation de paramètres, une nouvelle instanciation se produirait, cependant, les membres de ce foncteur seraient toujours partagés (c'est-à-dire les arguments capturés).

Sébastien Mach
la source
6
Cette proposition de laisser tomber le spécificateur de type est tout à fait grotesque.
Courses de légèreté en orbite le
Ils sont entrés avec g ++ - 4.9 . Vous devez fournir -std=c++1y.
emsr
Je n'avais pas réalisé qu'ideone n'avait pas encore gcc-4.9 et C ++ 14.
emsr
question: est-ce que cela autoa les mêmes règles de déduction que l'automobile classique? Si nous nous référons à l'analogie basée sur un modèle, cela signifierait que l'auto n'est pas automatique, ce sont les mêmes règles que la déduction de type de modèle. La question est alors: la déduction de modèle est-elle équivalente à auto?
v.oddou
@ v.oddou: "Classic auto", c'est bien. Pour moi, "classic auto" signifie "Stack Variable", et était autrefois utilisé en contraste avec staticou register:) Quoi qu'il en soit, oui, l'utiliser autosignifie que sous le capot, un modèle normal est généré. En fait, un lambda sera remplacé en interne par une classe de foncteur, et un autoparamètre signifie qu'il template <T> ... (T ...)sera émis.
Sebastian Mach
17

C'est une fonctionnalité proposée en C ++ 14 (pas en C ++ 11) similaire (ou même équivalente) aux modèles. Par exemple, N3559 fournit cet exemple:

Par exemple, cette instruction contenant une expression lambda générique:

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

peut entraîner la création d'un type de fermeture et d'un objet qui se comporte de la même manière que la structure ci-dessous:

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
la source