Lambda capture as const reference?
Asked Answered
J

8

225

Is it possible to capture by const reference in a lambda expression?

I want the assignment marked below to fail, for example:

#include <algorithm>
#include <string>

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;
}

Update: As this is an old question, it might be good to update it if there are facilities in C++14 to help with this. Do the extensions in C++14 allow us to capture a non-const object by const reference? (August 2015)

Jeffers answered 22/9, 2010 at 19:23 Comment(4)
shouldn't your lambda look like: [&, &best_string](string const s) { ...}?Martensite
really inconsistent capture. "const &" can be very useful when you have large const object which should be accessed but not modified in lambda functionSutra
looking at the code. you could use a two parameter lambda and bind the second as a const reference. comes with a cost though.Brozak
This isn't possible in C++11 it would seem. But perhaps we can update this question for C++14 - are there extensions that allow this? The C++14 generalized lambda captures?Anticline
A
145

const isn't in the grammar for captures as of n3092:

capture:
  identifier
  & identifier
  this

The text only mention capture-by-copy and capture-by-reference and doesn't mention any sort of const-ness.

Feels like an oversight to me, but I haven't followed the standardization process very closely.

Abaft answered 22/9, 2010 at 19:40 Comment(4)
I just tracked a bug back to a variable being modified from the capture that was mutable, but should have been const. Or more correctly, if the capture variable was const, the compiler would have enforced the correct behavior on the programmer. It'd be nice if the syntax supported [&mutableVar, const &constVar].Lexicography
It seems like this should be possible with C++14, but I can't get it to work. Any suggestions?Anticline
Constness is inherited from the captured variable. So if you want to capture a as const, declare const auto &b = a; before the lambda and capture bMedlar
@Medlar Bleargh. Except apparently this doesn't apply when capturing a member-variable by reference: [&foo = this->foo] inside of a const function gives me an error stating that the capture itself discards qualifiers. This could be a bug in GCC 5.1, though, I suppose.Cheesewood
C
189

In using static_cast / const_cast:

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

DEMO


In using std::as_const:

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

DEMO 2

Corselet answered 7/9, 2015 at 13:56 Comment(6)
Also, perhaps this should be edited into the accepted answer? Either way, there should be one good answer that covers both c++11 and c++14. Although, I guess it could be argued that c++14 will be good enough for everyone over the coming yearsAnticline
@AaronMcDaid const_cast can unconditionally change a volatile object to a const object (when asked to cast to const), thus, for adding constraints I prefer static_castCorselet
@PiotrSkotnicki on the other hand, static_cast to const reference may silently create a temporary if you didn't get the type exactly rightAultman
@Aultman &basic_string = std::as_const(best_string) should solve all problemsCorselet
@PiotrSkotnicki Except the problem of that being a hideous way to write something that should be as simple as const& best_string.Cheesewood
You can actually just write best_string = std::cref(best_string) from c++14 onwards. See Sergey's answer for an explanation, or this example.Suk
A
145

const isn't in the grammar for captures as of n3092:

capture:
  identifier
  & identifier
  this

The text only mention capture-by-copy and capture-by-reference and doesn't mention any sort of const-ness.

Feels like an oversight to me, but I haven't followed the standardization process very closely.

Abaft answered 22/9, 2010 at 19:40 Comment(4)
I just tracked a bug back to a variable being modified from the capture that was mutable, but should have been const. Or more correctly, if the capture variable was const, the compiler would have enforced the correct behavior on the programmer. It'd be nice if the syntax supported [&mutableVar, const &constVar].Lexicography
It seems like this should be possible with C++14, but I can't get it to work. Any suggestions?Anticline
Constness is inherited from the captured variable. So if you want to capture a as const, declare const auto &b = a; before the lambda and capture bMedlar
@Medlar Bleargh. Except apparently this doesn't apply when capturing a member-variable by reference: [&foo = this->foo] inside of a const function gives me an error stating that the capture itself discards qualifiers. This could be a bug in GCC 5.1, though, I suppose.Cheesewood
F
16

I think the capture part should not specify const, as the capture means, it only need a way to access the outer scope variable.

The specifier is better specified in the outer scope.

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

lambda function is const(can't change value in its scope), so when you capture variable by value, the variable can not be changed, but the reference is not in the lambda scope.

Fatimafatimah answered 4/1, 2014 at 3:20 Comment(5)
@Amarnath Balasubramani: It's just my opinion, I think there is no need to specify a const reference in lambda capture part, why should there is a variable const here and not const at another place(if that possible, it will be error-prone). happy to see your response anyway.Fatimafatimah
If you need to modify better_string within the containing scope, then this solution won't work. The use case for capturing as const-ref is when the variable needs to be mutable in the containing scope but not within the lambda.Varico
@JonathanSharman, it doesn't cost you anything to create a const reference to a variable, so you can make a const string &c_better_string = better_string; and happily pass it to the lambda: [&c_better_string]Trumpetweed
@Trumpetweed The problem with that is you're introducing an extra variable name into the surrounding scope. I think Piotr Skotnicki's solution above is the cleanest, as it achieves const-correctness while keeping variable scopes minimal.Varico
@JonathanSharman, here we enter the land of opinions - what is the prettiest, or cleanest, or whatever. My point is is that both solutions are suitable for the task.Trumpetweed
I
16

There is a shorter way.

Note that there is no ampersand before "best_string".

It will be of a const std::reference_wrapper<T> type.

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

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

Iceberg answered 13/9, 2017 at 16:52 Comment(0)
X
9

I guess if you're not using the variable as a parameter of the functor, then you should use the access level of the current function. If you think you shouldn't, then separate your lambda from this function, it's not part of it.

Anyway, you can easily achieve the same thing that you want by using another const reference instead :

#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;
}

But that's the same as assuming that your lambda have to be isolated from the current function, making it a non-lambda.

Xenogenesis answered 22/9, 2010 at 21:23 Comment(6)
The capture clause still mentions best_string only. Apart from that, GCC 4.5 "successfully rejects" the code like intended.Summertime
Yes, this would give me the results I was trying to achieve on a technical level. Ultimately however, the answer to my original question seems to be "no."Jeffers
Why would that make it a "non-lambda"?Furry
Because the nature of a lambda is that it's context-dependant. If you don't need a specific context then it's just a quick way to make a functor. If the functor should be context-independant, make it a real functor.Xenogenesis
Note that there is std::begin/end which also works for raw arrays and makes it consistent and more readable to get the iterators for (almost?) any type of sequence. Also, there now is the range-based for loop: for(auto variable : sequence)Gameness
"If the functor should be context-independant, make it a real functor" ... and kiss possible inlining goodbye?Bilharziasis
B
5

I think you have three different options:

  • don't use const reference, but use a copy capture
  • ignore the fact that it is modifiable
  • use std::bind to bind one argument of a binary function which has a const reference.

using a copy

The interesting part about lambdas with copy captures is that those are actually read only and therefore do exactly what you want them to.

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

using std::bind

std::bind reduces the arity of a function. Note however that this might/will lead to an indirect function call via a function pointer.

int main() {
  int a = 5;
  std::function<int ()> f2 = std::bind( [](const int &a){return a;}, a);
}
Brozak answered 11/12, 2013 at 20:19 Comment(1)
Except changes to the variable in the containing scope will not be reflected in the lambda. It's not a reference, it's just a variable which shouldn't be reassigned because the reassignment wouldn't mean what it would appear to mean.Donettedoney
F
0

Use clang or wait until this gcc bug is fixed: bug 70385: Lambda capture by reference of const reference fails [https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70385]

Fewer answered 16/11, 2016 at 16:33 Comment(4)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes.”Kingdom
Ok, I edited my answer to add gcc bug description here.Fewer
This is quite an indirect answer the question, if any. The bug is about how a compiler fails when capturing something const, so perhaps why some way to address or work around the issue in the question might not work with gcc.Flag
For any readers, this seems to be fixed from gcc 7.1 onwards: godbolt.org/z/c8MnnY7xYSuk
Y
-2

Using a const will simply have the algorithm ampersand set the string to it's original value, In other words, the lambda won't really define itself as parameter of the function, though the surrounding scope will have an extra variable... Without defining it though, it wouldn't define the string as the typical [&, &best_string](string const s) Therefore, its most likely better if we just leave it at that, trying to capture the reference.

Youngstown answered 2/2, 2018 at 20:24 Comment(1)
Its a very old question: your answer is lacking context related to which C++ version you are referring to. Please provide this content.Antler

© 2022 - 2024 — McMap. All rights reserved.