web-dev-qa-db-fra.com

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 modelée.)

Je demande si vous pouvez faire quelque chose comme:

template <class Functor> 
struct Foo { };
// ...
Foo<decltype([]()->void { })> foo;

Cela serait utile dans les cas où, par exemple, un modèle de classe a divers paramètres comme equal_to ou quelque chose qui est généralement implémenté en tant que foncteurs à une ligne. Par exemple, supposons que je veuille instancier une table de hachage qui utilise ma propre fonction de comparaison d'égalité personnalisée. Je voudrais pouvoir dire quelque chose comme:

typedef std::unordered_map<
  std::string,
  std::string,
  std::hash<std::string>,
  decltype([](const std::string& s1, const std::string& s2)->bool 
    { /* Custom implementation of equal_to */ })
  > map_type;

Mais j'ai testé cela 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 je me trompe et c'est permis, mais GCC est juste en retard dans leur mise en œuvre?

59
Channel72

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é (comme decltype et sizeof, entre autres). C++ 0x FDIS, 5.1.2 [expr.prim.lambda] p2

L'évaluation d'une expression lambda donne une valeur temporaire (12.2). Ce temporaire est appelé l'objet de fermeture. ne expression lambda ne doit pas apparaître dans un opérande non évalué (article 5). [Remarque: un objet de fermeture se comporte comme un objet fonction (20.8) .— note de fin] (accent sur le mien)

Vous devez d'abord créer un lambda spécifique, puis utiliser decltype sur celui-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;

C'est parce que chaque objet de fermeture dérivé de lambda peut avoir un type complètement différent, ils sont comme des fonctions anonymes après tout.

52
Xeo

@Xeo vous a donné la raison, donc je vais vous donner le travail.

Souvent, si 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::string>,
  std::function<bool(std::string const&, std::string const&)>
  > map_type;

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

Ensuite, vous pouvez simplement écrire le 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 qu'il corresponde au comportement. Les objets qui se comparent égaux doivent avoir le même hachage.

9
Matthieu M.

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

Si votre lambda est apatride (pas de capture), alors ça devrait aller. Dans ce cas, le lambda se désintègre en un pointeur de fonction ordinaire, que vous pouvez utiliser comme argument de modèle au lieu d'un type lambda.

gcc ne l'aime pas cependant. http://ideone.com/bHM3n

5
Ben Voigt

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

0
Puppy