Capture Lambda comme référence const?

166

Est-il possible de capturer par référence const dans une expression lambda?

Je souhaite que le devoir indiqué ci-dessous échoue, par exemple:

#include <cstdlib>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";

    for_each( &strings[0], &strings[num_strings], [&best_string](const string& s)
      {
        best_string = s; // this should fail
      }
    );
    return 0;
}

Mise à jour: Comme il s'agit d'une vieille question, il pourrait être bon de la mettre à jour s'il existe des fonctionnalités dans C ++ 14 pour vous aider. Les extensions en C ++ 14 nous permettent-elles de capturer un objet non const par référence const? ( Août 2015 )

John Dibling
la source
votre lambda ne devrait-il pas ressembler à [&, &best_string](string const s) { ...}:?
erjot le
3
capture vraiment incohérente. "const &" peut être très utile lorsque vous avez un grand objet const qui doit être accessible mais non modifié dans la fonction lambda
sergtk
en regardant le code. vous pouvez utiliser un lambda à deux paramètres et lier le second comme référence const. vient avec un coût cependant.
Alex
1
Cela ne semble pas possible dans C ++ 11. Mais peut-être pouvons-nous mettre à jour cette question pour C ++ 14 - y a-t-il des extensions qui permettent cela? Les captures lambda généralisées C ++ 14?
Aaron McDaid

Réponses:

127

const n'est pas dans la grammaire des captures à partir de n3092:

capture:
  identifier
  & identifier
  this

Le texte ne mentionne que la capture par copie et la capture par référence et ne mentionne aucune sorte de const-ness.

Cela me semble un oubli, mais je n'ai pas suivi de très près le processus de normalisation.

Steve M
la source
47
Je viens de suivre un bug vers une variable en cours de modification à partir de la capture qui était mutable, mais qui aurait dû l'être const. Ou plus correctement, si la variable de capture était const, le compilateur aurait imposé le comportement correct au programmeur. Ce serait bien si la syntaxe était prise en charge [&mutableVar, const &constVar].
Sean
Il semble que cela devrait être possible avec C ++ 14, mais je ne peux pas le faire fonctionner. Aucune suggestion?
Aaron McDaid
38
Constness est héritée de la variable capturée. Donc, si vous voulez capturer en atant que const, déclarez const auto &b = a;avant le lambda et capturezb
StenSoft
7
@StenSoft Bleargh. Sauf qu'apparemment, cela ne s'applique pas lors de la capture d'une variable membre par référence: à l' [&foo = this->foo]intérieur d'une constfonction me donne une erreur indiquant que la capture elle-même rejette les qualificatifs. Cela pourrait être un bogue dans GCC 5.1, cependant, je suppose.
Kyle Strand
119

Dans en utilisant static_cast/ const_cast:

[&best_string = static_cast<const std::string&>(best_string)](const string& s)
{
    best_string = s; // fails
};

DEMO


Dans utilisant std::as_const:

[&best_string = std::as_const(best_string)](const string& s)
{
    best_string = s; // fails
};

DÉMO 2

Piotr Skotnicki
la source
Aussi, peut-être que cela devrait être modifié dans la réponse acceptée? Quoi qu'il en soit, il devrait y avoir une bonne réponse qui couvre à la fois c ++ 11 et c ++ 14. Bien que, je suppose que l'on pourrait faire valoir que c ++ 14 sera assez bon pour tout le monde dans les années à venir
Aaron McDaid
12
@AaronMcDaid const_castpeut changer sans condition un objet volatil en un objet const (lorsqu'on lui demande de le convertir const), ainsi, pour ajouter des contraintes que je préfèrestatic_cast
Piotr Skotnicki
1
@PiotrSkotnicki d'autre part, la static_castréférence à const peut créer silencieusement un temporaire si vous n'avez pas obtenu le type exactement
MM
24
@MM &basic_string = std::as_const(best_string)devrait résoudre tous les problèmes
Piotr Skotnicki
14
@PiotrSkotnicki Sauf que c'est une manière hideuse d'écrire quelque chose qui devrait être aussi simple que const& best_string.
Kyle Strand
13

Je pense que la partie de capture ne devrait pas spécifier const, comme moyen de capture, il suffit d'un moyen d'accéder à la variable de portée externe.

Le spécificateur est mieux spécifié dans la portée externe.

const string better_string = "XXX";
[&better_string](string s) {
    better_string = s;    // error: read-only area.
}

La fonction lambda est const (ne peut pas changer la valeur dans sa portée), donc lorsque vous capturez une variable par valeur, la variable ne peut pas être modifiée, mais la référence n'est pas dans la portée lambda.

zhb
la source
1
@Amarnath Balasubramani: C'est juste mon avis, je pense qu'il n'est pas nécessaire de spécifier une référence const dans la partie capture lambda, pourquoi devrait-il y avoir une variable const ici et non const à un autre endroit (si cela est possible, ce sera sujet aux erreurs ). heureux de voir votre réponse de toute façon.
zhb
2
Si vous devez modifier better_stringdans la portée contenant, cette solution ne fonctionnera pas. Le cas d'utilisation de la capture en tant que const-ref est lorsque la variable doit être mutable dans la portée contenant mais pas dans le lambda.
Jonathan Sharman
@JonathanSharman, cela ne vous coûte rien de créer une référence const à une variable, vous pouvez donc en faire un const string &c_better_string = better_string;et le passer avec plaisir au lambda:[&c_better_string]
Steed
@Steed Le problème avec cela est que vous introduisez un nom de variable supplémentaire dans la portée environnante. Je pense que la solution de Piotr Skotnicki ci-dessus est la plus propre, car elle permet d'obtenir une correction constante tout en gardant les portées variables minimales.
Jonathan Sharman
@JonathanSharman, nous entrons ici dans le pays des opinions - quelle est la plus jolie, la plus propre ou quoi que ce soit. Mon point est que les deux solutions sont adaptées à la tâche.
Steed
8

Je suppose que si vous n'utilisez pas la variable comme paramètre du foncteur, vous devriez utiliser le niveau d'accès de la fonction actuelle. Si vous pensez que vous ne devriez pas, alors séparez votre lambda de cette fonction, cela n'en fait pas partie.

Quoi qu'il en soit, vous pouvez facilement obtenir la même chose que vous voulez en utilisant une autre référence const à la place:

#include <cstdlib>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

int main()
{
    string strings[] = 
    {
        "hello",
        "world"
    };
    static const size_t num_strings = sizeof(strings)/sizeof(strings[0]);

    string best_string = "foo";
    const string& string_processed = best_string;

    for_each( &strings[0], &strings[num_strings], [&string_processed]  (const string& s)  -> void 
    {
        string_processed = s;    // this should fail
    }
    );
    return 0;
}

Mais c'est la même chose que de supposer que votre lambda doit être isolé de la fonction actuelle, ce qui en fait un non-lambda.

Klaim
la source
1
La clause de capture ne mentionne toujours best_stringque. En dehors de cela, GCC 4.5 "rejette avec succès" le code comme prévu.
sellibitze
Oui, cela me donnerait les résultats que j'essayais d'obtenir sur le plan technique. En fin de compte, cependant, la réponse à ma question initiale semble être «non».
John Dibling
Pourquoi cela en ferait-il un "non-lambda"?
Parce que la nature d'un lambda est qu'il dépend du contexte. Si vous n'avez pas besoin d'un contexte spécifique, c'est juste un moyen rapide de créer un foncteur. Si le foncteur doit être indépendant du contexte, faites-en un véritable foncteur.
Klaim
3
"Si le foncteur doit être indépendant du contexte, en faire un vrai foncteur" ... et dire adieu possible en ligne?
Andrew Lazarus
5

Je pense que vous avez trois options différentes:

  • n'utilisez pas de référence const, mais utilisez une capture de copie
  • ignorer le fait qu'il est modifiable
  • utilisez std :: bind pour lier un argument d'une fonction binaire qui a une référence const.

en utilisant une copie

La partie intéressante à propos des lambdas avec des captures de copie est que celles-ci sont en réalité en lecture seule et font donc exactement ce que vous voulez.

int main() {
  int a = 5;
  [a](){ a = 7; }(); // Compiler error!
}

en utilisant std :: bind

std::bindréduit l'arité d'une fonction. Notez cependant que cela pourrait / conduira à un appel de fonction indirect via un pointeur de fonction.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}
Alex
la source
1
Sauf que les modifications apportées à la variable dans la portée contenante ne seront pas reflétées dans le lambda. Ce n'est pas une référence, c'est juste une variable qui ne devrait pas être réaffectée car la réaffectation ne signifierait pas ce qu'elle semble signifier.
Grault du
4

Il existe un moyen plus court.

Notez qu'il n'y a pas d'esperluette avant "best_string".

Il sera de type "const std :: reference_wrapper << T >>".

[best_string = cref(best_string)](const string& s)
{
    best_string = s; // fails
};

http://coliru.stacked-crooked.com/a/0e54d6f9441e6867

Sergey Palitsin
la source
0

Utilisez clang ou attendez que ce bogue gcc soit corrigé: bogue 70385: La capture Lambda par référence à la référence const échoue [ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385 ]

user1448926
la source
1
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change. »
Div
Ok, j'ai édité ma réponse pour ajouter une description du bogue gcc ici.
user1448926
C'est une réponse assez indirecte à la question, le cas échéant. Le bogue concerne la façon dont un compilateur échoue lors de la capture de quelque chose de const, alors peut-être pourquoi un moyen de résoudre ou de contourner le problème dans la question pourrait ne pas fonctionner avec gcc.
Stein
0

L'utilisation d'un const aura simplement l'esperluette de l'algorithme et définira la chaîne sur sa valeur d'origine.En d'autres termes, le lambda ne se définira pas vraiment comme paramètre de la fonction, bien que la portée environnante aura une variable supplémentaire ... Sans la définir cependant, cela ne définirait pas la chaîne comme la chaîne typique [&, & best_string] (string const s). Par conséquent , il est plus probable que nous nous en tenions à cela, essayant de capturer la référence.

Saith
la source
C'est une question très ancienne: votre réponse manque de contexte lié à la version C ++ à laquelle vous faites référence. Veuillez fournir ce contenu.
ZF007 le