Search code examples
c++lambdaconst-correctness

Why would a pointer declared const change in a lambda?


I am running gcc 11.4.0 under PopOS 22.04 LTS. I am attempting to create a "conditional callback" with a lambda using references and pointers, like this:

#include <iostream>

struct A {
  int i;
};

class AProxy {
 private:
  A* const m_stored;
 public:
  AProxy(A& stored) : m_stored(&stored) {}
  void print() { std::cout << "i is " << m_stored->i << std::endl; }
};

class Holder {
 private:
  A& m_stored1;
  A& m_stored2;

 public:
  Holder(A& stored1, A& stored2) : m_stored1(stored1), m_stored2(stored2) {}
  void print() {
    std::cout << "i is " << m_stored1.i << std::endl;
  }
  void swap() {
    std::swap(m_stored1, m_stored2);
  }
  AProxy getstored1() { return {m_stored1}; }
  AProxy getstored2() { return {m_stored2}; }
};


int main() {
  A foo;
  A bar;
  foo.i = 5;
  bar.i = 6;

  Holder holder = {foo, bar};

  AProxy fooproxy = holder.getstored1();
  AProxy barproxy = holder.getstored2();

  auto foofunc = [fooproxy]() {
    AProxy copy = {fooproxy};
    copy.print();
  };

  foofunc(); //Prints 5
  holder.swap();
  foofunc(); //Prints 6?

  return 0;
}

My question is this: Since I told the compiler not to change what m_stored points to in AProxy, I would expect it to capture the reference to foo and hold on to it.
Why would it suddenly change out from under me like that?


Solution

  • Since I told the compiler not to change what m_stored points to in AProxy, I would expect it to capture the reference to foo and hold on to it.

    That is exactly what it did.

    Why would it suddenly change out from under me like that?

    It isn't.

    const applies to the thing on its left, unless there is nothing there, then it applies to the thing on its right.

    Your use of const applies to the AProxy::m_stored pointer itself. You are telling the compiler to disallow changes to the pointer only.

    But, you are not telling the compiler to disallow changes to the A object that the m_stored pointer is pointing at.

    Only the pointer itself is const, not the object.

    You are printing the value of foo.i, then swapping the value of foo.i with bar.i, then printing foo.i again. The pointers don't change, but the content of the objects they point to do change. That is why you see the result you are seeing.

    You would have to make Holder hold references to const A objects, then swap() won't compile anymore. And you should make AProxy hold a pointer to a const A object, since it only ever reads from the object, never writes to it.

    But, that still won't stop any other code that has direct access to foo and bar from making changes to them. The original objects are simply not const to begin with, even if you use const references/pointers to them. If you don't want the objects to ever change, make them const to begin with, eg:

    const A foo{5};
    const A bar{6};