J'ai fait le tour des warning de ggc, je n'ai rien trouvé qui pourrait activer une erreur sur ce problème. C'est effectivement dommage.
Ce n'est pas vraiment un problème de déduction automatique du type de retour d'une fonction lambda. A partir du moment que l'on comprend que :
1 2
| []() { return expression; }
[]() ->decltype(expression) { return expression; } |
L'erreur serait effectivement de penser que la capture est prise en compte (en particulier "la capture par référence n’y fait rien")
Ainsi, dans le code d'exemple proposé, si on déclare explicitement le même type de retour que ce que l'on a avec la déduction automatique, le problème persiste :
std::function<const A&()> f = [&a]() -> A {return a;};
En revanche, si on écrit explicitement que l'on ne souhaite pas retourner a, mais une référence sur a (avec std::ref ou cref) ou si on écrit explicitement le type de retour avec référence, le problème est réglé :
1 2
| std::function<const A&()> f = [&a]{return std::cref(a);};
std::function<const A&()> f = [&a]() -> A const& {return a;}; |
La difficulté vient du fait que l'on manipule 3 types (le type de la lambda, le type de f et le type de f()) et que le passage "d'une référence à une copie" peut être silencieux :
1 2
| std::function<A()> f = [&a]() {return a;};
std::function<const A&()> g = f; |
ne produit pas de warning
On peut utiliser plusieurs écritures différentes, qui ne sont pas forcement équivalentes :
1 2 3 4 5 6 7 8 9 10
| lambda f result
1 std::function<A()> f = [&a]() {return a;}; A() A() ok
2 std::function<A const& ()> f = [&a]() {return a;}; A() A const& () erreur
3 std::function<A const& ()> f = [&a]() -> A {return a;}; A() A const& () erreur
4 std::function<A const& ()> f = [&a]() {return cref(a);}; A const& () A const& () ok
5 std::function<A const& ()> f = [&a]() -> A const& {return a;}; A const& () A const& () ok
6 auto f = [&a]() {return a;}; A() A() ok
7 auto f = [&a]() -> A {return a;}; A() A() ok
8 auto f = [&a]() {return cref(a);}; A const& () A const& () ok
9 auto f = [&a]() -> A const& {return a;}; A const& () A const& () ok |
Pour 4, 5, 8 et 9, le type de la lambda est conservé (retour par référence), on initialise aref avec une référence sur a, tout va bien.
Pour 1, 6 et 7, le type de la lambda est également conservé (retour par copie), on initialise aref avec un copie temporaire, ce qui prolonge la durée de vie de ce temporaire. Par contre, on n'a pas forcement le comportement attendu : on ne travaille pas sur une référence sur a, mais sur une copie temporaire.
Pour 2 et 3, il y a une conversion du type de la fonction, qui passe de A() en A const& (). Cette conversion est silencieuse (aucun warning). Et comme le type de retour de la fonction est une référence, il n'y a pas prolongation de la durée de vie du temporaire. C'est moche...
Quelles régles de codage peut on en déduire ?
* utiliser autant que possible auto
* utiliser explicitement ref et cref : a pour retour par copie, ref(a) pour retour par référence, cref(a) pour retour par référence constante
En complément, lire les GOTW 1 et 2 récents (http://herbsutter.com/2013/05/09/gotw-1-solution/ et http://herbsutter.com/2013/05/13/got...orary-objects/), on peut également écrire :
1 2
| std::function<A const& ()> f = [&a]() {return a;};
const A& aref = { f() }; |
qui évite les conversions implicites
Partager