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.