6

I have a function that uses a lambda expression.

std::vector<Bar*> mBars;

void foo(Bar* bar)
{
    auto duplicateBars = std::remove_if(mBars.begin(), mBars.end(),
        [bar] (const Bar* const &element)
        {
            return bar == element;
        });

    mBars.erase(duplicateBars, mBars.end());
}

Later, I reviewed the code and realized I could add two consts to foo's signature.

void foo(const Bar* const bar);

bar's pointer and data is now constant, but for the purpose of the lambda expression the pointer itself is constant, because I captured by value. However, the data pointed to can be changed and there is no way to change this, because const is not allowed in the lambda capture.

This is unintuitive to me. Is my interpretation correct? I can use the second signature, but I cannot protect the data from being changed in the lambda expression.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
user870130
  • 565
  • 1
  • 8
  • 16
  • What exactly are you trying to achieve? Pointers can be used, const or not, in lambdas: http://ideone.com/GalbBA – Bruno Ferreira May 28 '14 at 18:25
  • I am trying to achieve const-correctness. – user870130 May 28 '14 at 18:26
  • Are you changing the `bar` inside `foo()`? If not, just use `Bar const * bar` (same as `const Bar *`). A const pointer means that the data is const, not the pointer itself. – Bruno Ferreira May 28 '14 at 18:28
  • 3
    Ok, now what exactly is the problem? If `foo` is defined as `foo(Bar const * const bar)` then you cannot use the `bar` captured by the lambda to modify whatever the `bar` passed to `foo` points to. – Praetorian May 28 '14 at 18:33
  • This question seems to be fit to codereview.se – Bruno Ferreira May 28 '14 at 18:36
  • I am not changing bar inside foo() and that is the crux of the question. My understanding is capture by value will make the pointer const. However, there is no way to make the data const within the lambda expression. – user870130 May 28 '14 at 18:36
  • 2
    Use `[bar] (const Bar* element)` for the lambda and `void foo(const Bar* bar)`. Now both are const. – Bruno Ferreira May 28 '14 at 18:38
  • Nobody is talking about modifying `bar` outside of the body of the lambda. Try either `++bar` or `bar->something = whatever` within the body of the lambda expression, neither will compile. So, once again, what is your question? – Praetorian May 28 '14 at 18:39
  • @BrunoFerreira I understand the use of the lambda now. I am curious why you suggest void foo(const Bar* bar) instead of void foo(const Bar* const bar), if bar's data and pointer is not meant to be changed in function foo. – user870130 May 28 '14 at 20:36
  • I see the reason. I am passing the pointer by value. Using a const is only keeping me from changing the local value. – user870130 May 28 '14 at 22:30

2 Answers2

11

However, the data pointed to can be changed and there is no way to change this, because const is not allowed in the lambda capture.

No, when capturing by value in a lambda expression constness is preserved, i.e. capturing a pointer to const data will prevent changes to the data inside the lambda.

int i = 1;
const int* ptr = &i;

auto func = [ptr] {
    ++*ptr; // ERROR, ptr is pointer to const data.
}

A lambda will also add top-level constness to pointers when capturing by value (unless using mutable).

auto func = [ptr] {
    ptr = nullptr; // ERROR, ptr is const pointer (const int* const).
}

auto func = [ptr] () mutable { // Mutable, will not add top-level const.
    ptr = nullptr; // OK
}

I can use the second signature, but I cannot protect the data from being changed in the lambda expression.

You can protect the data from being changed inside the lambda by using const.

const Bar* bar = &bar_data;
auto b = [bar] (const Bar* element) { // Data pointed to by bar is read-only.
    return bar == element;
};

Also the lambda expression takes a parameter of type const Bar* const &, i.e. reference to const pointer to const data. No need to take a reference, simply take a const Bar*.

More info about pointers and const: What is the difference between const int*, const int * const, and int const *?

Community
  • 1
  • 1
Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • 1
    Could you explain your last point, "lambda expression takes a parameter of type const Bar* const &, i.e. reference to const pointer to const data. No need to take a reference, simply take a const Bar*." – user870130 May 28 '14 at 20:29
  • 2
    @user870130 Passing a *reference* to a pointer is only useful if you intend to modify the pointer passed to the lambda from inside the lambda. In your case you only check pointer equality so `const Bar*` is enough. – Felix Glas May 28 '14 at 21:27
3

Your question seems to arise from a misunderstanding of how capturing of variables in lambda expressions works. When you capture a variable by copy, the corresponding data member created in the closure type generated from the lambda expression will have the same type as the original object. This preserves const-ness, and you cannot go modify whatever bar points to within the body of the lambda.

From §5.1.2/15 [expr.prim.lambda]

An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that is not of the form & identifier or & identifier initializer. For each entity captured by copy, an unnamed non-static data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise.

Live demo

Praetorian
  • 106,671
  • 19
  • 240
  • 328