Le +
dans l'expression +[](){}
est l' +
opérateur unaire . Il est défini comme suit dans [expr.unary.op] / 7:
L'opérande de l' +
opérateur unaire doit avoir le type arithmétique, énumération sans portée ou pointeur et le résultat est la valeur de l'argument.
Le lambda n'est pas de type arithmétique etc., mais il peut être converti:
[expr.prim.lambda] / 3
Le type de l' expression lambda est un [...] type de classe non-union unique et sans nom - appelé type de fermeture - dont les propriétés sont décrites ci-dessous.
[expr.prim.lambda] / 6
Le type de fermeture pour un lambda-expression sans lambda-capture a une public
non virtual
non explicit
const
fonction de conversion de pointeur vers une fonction ayant les mêmes paramètres et types de retour comme opérateur d'appel de fonction du type de fermeture. La valeur renvoyée par cette fonction de conversion doit être l'adresse d'une fonction qui, lorsqu'elle est invoquée, a le même effet que l'appel de l'opérateur d'appel de fonction du type de fermeture.
Par conséquent, l'unaire +
force la conversion vers le type de pointeur de fonction, qui est pour ce lambda void (*)()
. Par conséquent, le type de l'expression +[](){}
est ce type de pointeur de fonction void (*)()
.
La deuxième surcharge void foo(void (*f)())
devient une correspondance exacte dans le classement pour la résolution de surcharge et est donc choisie sans ambiguïté (car la première surcharge n'est PAS une correspondance exacte).
Le lambda [](){}
peut être converti en std::function<void()>
via le ctor de modèle non explicite de std::function
, qui prend tout type qui remplit les conditions Callable
et CopyConstructible
.
Le lambda peut également être converti en void (*)()
via la fonction de conversion du type de fermeture (voir ci-dessus).
Les deux sont des séquences de conversion définies par l'utilisateur et du même rang. C'est pourquoi la résolution de surcharge échoue dans le premier exemple en raison d'une ambiguïté.
Selon Cassio Neri, soutenu par un argument de Daniel Krügler, cette +
astuce unaire devrait être un comportement spécifié, c'est-à-dire que vous pouvez vous y fier (voir discussion dans les commentaires).
Néanmoins, je recommanderais d'utiliser un cast explicite vers le type de pointeur de fonction si vous voulez éviter l'ambiguïté: vous n'avez pas besoin de demander à SO ce que cela fait et pourquoi cela fonctionne;)
std::bind
à unstd::function
objet qui peut être appelé de la même manière qu'une fonction lvalue.operator +()
à un type de fermeture sans état. Supposons que cet opérateur renvoie autre chose que le pointeur vers la fonction vers laquelle le type de fermeture est converti. Ensuite, cela modifierait le comportement observable d'un programme qui viole 5.1.2 / 3. S'il vous plaît, faites-moi savoir si vous êtes d'accord avec ce raisonnement.operator +
, mais cela se compare à la situation où il n'y a pasoperator +
pour commencer. Mais il n'est pas spécifié que le type de fermeture ne doit pas avoir deoperator +
surcharge. "Une implémentation peut définir le type de fermeture différemment de ce qui est décrit ci-dessous à condition que cela ne modifie pas le comportement observable du programme autrement que par [...]" mais IMO l' ajout d' un opérateur ne change pas le type de fermeture en quelque chose de différent de ce que est "décrit ci-dessous".operator +()
est exactement celle décrite par la norme. La norme permet à une implémentation de faire quelque chose de différent de ce qui est spécifié. Par exemple, ajouteroperator +()
. Cependant, si cette différence est observable par un programme, alors elle est illégale. Une fois, j'ai demandé dans comp.lang.c ++. Modéré si un type de fermeture pouvait ajouter un typedef pourresult_type
et l'autretypedefs
requis pour les rendre adaptables (par exemple parstd::not1
). On m'a dit que ce n'était pas possible parce que c'était observable. Je vais essayer de trouver le lien.