65 votes

Expressions Lambda en tant que paramètres de modèle de classe

Les expressions lambda peuvent-elles être utilisées comme paramètres de modèle de classe? (Notez que c'est une question très différente de celle-ci, qui demande si une expression lambda elle-même peut être templatisée.)

Je demande si vous pouvez faire quelque chose comme :

template  
struct Foo { };
// ...
Foovoid { })> foo;

Ce serait utile dans des cas où, par exemple, un modèle de classe a divers paramètres comme equal_to ou quelque chose du genre, qui sont généralement implémentés comme des foncteurs d'une ligne. Par exemple, supposons que je veuille instancier une table de hachage qui utilise ma propre fonction de comparaison d'égalité personnalisée. J'aimerais être capable de dire quelque chose comme :

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash,
  decltype([](const std::string& s1, const std::string& s2)->bool 
    { /* Implémentation personnalisée de equal_to */ })
  > map_type;

Mais j'ai testé ceci sur GCC 4.4 et 4.6, et cela ne fonctionne pas, apparemment parce que le type anonyme créé par une expression lambda n'a pas de constructeur par défaut. (Je me souviens d'un problème similaire avec boost::bind.) Y a-t-il une raison pour laquelle le projet de norme ne le permet pas, ou est-ce que je me trompe et que c'est autorisé mais que GCC est simplement en retard dans leur implémentation?

58voto

Xeo Points 69818

Dès C++20, cette réponse est maintenant obsolète. C++20 introduit des lambdas sans état dans des contextes non évalués1:

Cette restriction a été initialement conçue pour empêcher que les lambdas n'apparaissent dans des signatures, ce qui aurait ouvert une boîte de vers pour le mangled, car les lambdas doivent avoir des types uniques. Cependant, la restriction est beaucoup plus forte qu'elle ne devrait l'être, et il est en effet possible d'obtenir le même effet sans elle.

Certaines restrictions sont toujours en place (par exemple, les lambdas ne peuvent toujours pas apparaître dans les signatures de fonction), mais l'utilisation décrite est maintenant complètement valide et la déclaration d'une variable n'est plus nécessaire.


Je demande si vous pouvez faire quelque chose comme :

Foo<decltype([]()->void { })> foo;

Non, vous ne pouvez pas, car les expressions lambda ne doivent pas apparaître dans un contexte non évalué (tel que decltype et sizeof, entre autres). C++0x FDIS, 5.1.2 [expr.prim.lambda] p2

L'évaluation d'une expression lambda aboutit à un prvalue temporaire (12.2). Ce temporaire est appelé l'objet de fermeture. Une expression lambda ne doit pas apparaître dans un opérande non évalué (Clause 5). [ Note : Un objet de fermeture se comporte comme un objet fonction (20.8).—fin de la note ] (soulignement ajouté)

Vous devriez d'abord créer une lambda spécifique, puis utiliser decltype sur celle-ci :

auto my_comp = [](const std::string& left, const std::string& right) -> bool {
  // whatever
}

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash<std::string>,
  decltype(my_comp)
  > map_type;

Cela est dû au fait que chaque objet de fermeture dérivé d'une lambda pourrait avoir un type complètement différent, ce sont comme des fonctions anonymes après tout.

3 votes

@Xeo les "fonctions anonymes" ne sont pas vraiment des fonctions. Cela devrait être dit à voix haute, car c'est confus.

0 votes

@Xeo connect.microsoft.com/VisualStudio/feedback/details/636117/… - Après avoir lu ceci, on se demande vraiment pourquoi quelqu'un a même eu l'idée de les appeler des fncs non nommées? Ne serait-il pas mieux et en fait correct s'ils étaient appelés objets officiellement non nommés? Je ne vais jamais, jamais appeler une expression lambda une fnc non nommée pour la simple raison que ce n'est tout simplement pas une fnc.

0 votes

@There: Sans captures, ils sont inommables fonctions. Avec des captures, ils sont inommables foncteurs. Utiliser "fncs" comme abréviation est vraiment mauvais car cela ne distingue pas les fonctions des functeurs.

16voto

Ap31 Points 2448

Réponse C++20 : oui !

Vous pouvez tout à fait faire quelque chose comme

Foovoid { })> foo;

Car c++20 autorise les lambdas sans état dans les contextes non évalués.

10voto

Matthieu M. Points 101624

@Xeo vous a donné la raison, donc je vais vous donner la solution de contournement.

Il arrive souvent que vous ne souhaitez pas nommer une fermeture, dans ce cas, vous pouvez utiliser std::function, qui est un type :

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash,
  std::function
  > map_type;

Notez qu'il capture exactement la signature de la fonction, sans plus.

Ensuite, vous pouvez simplement écrire la lambda lors de la construction de la carte.

Notez qu'avec unordered_map, si vous modifiez la comparaison d'égalité, vous feriez mieux de modifier le hachage pour correspondre au comportement. Les objets qui se comparent égaux doivent avoir le même hachage.

3 votes

Pas utile, std::function ne contient pas le comportement operator() que le type lambda actuel contient.

1 votes

@BenVoigt : En quoi est-ce différent? Je pensais que std::function était basé sur boost::function, qui se comporte de la même manière, n'est-ce pas?

0 votes

@Joseph : Le comportement est contenu dans une instance particulière de function. Mais le modèle utilise seulement le type, pas une instance, et donc il n'y a pas de comportement. L'implémentation de la table de hachage va créer une nouvelle instance chaque fois qu'elle veut invoquer le foncteur, et function est abstrait -- il ne peut pas être instancié.

5voto

Ben Voigt Points 151460

Vous ne pouvez pas faire cela avec une fermeture, car l'état n'est pas contenu dans le type.

Si votre lambda est sans état (pas de captures), alors vous devriez être bon. Dans ce cas, la lambda se dégrade en un pointeur de fonction ordinaire, que vous pouvez utiliser comme argument de modèle à la place d'un type lambda.

gcc n'aime pas ça cependant. http://ideone.com/bHM3n

0 votes

En utilisant l'astuce que Xeo a postée, vous pouvez utiliser des captures...vous auriez juste besoin de passer votre lambda à l'unordered_map dans le constructeur (car il ne peut pas en construire un lui-même).

1 votes

@ijpriest: Je ne vois rien dans la réponse de Xeo qui passe un objet lambda. L'objet lambda est créé dans le seul but d'être passé à decltype. Ce n'est pas à dire qu'une utilisation plus complexe n'est pas possible, mais je pense que vous rencontrerez des problèmes en ayant besoin que le lambda soit à portée globale afin de nommer l'instance du modèle, ce qui exclut la capture. Vous pourriez utiliser une combinaison de std :: function comme suggéré par @DeadMG et un objet lambda spécifié à l'exécution, mais alors vous sacrifiez l'efficacité du polymorphisme au moment de la compilation.

0 votes

@ijprest -- non, vous ne pouvez pas utiliser le contournement posté par Xeo pour cela. En essayant, une erreur comme celle-ci apparaît : erreur : utilisation de la fonction supprimée ‘::()’.

0voto

Puppy Points 90818

Vous devrez utiliser soit un type abstrait d'exécution, comme std::function, soit créer le type en tant que variable locale ou en tant que partie d'une classe templatisée.

2 votes

Je pense que le but est que le type exprime le comportement, et std::function n'exprime que la signature.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X