Search code examples
c++lambdac++14shared-ptr

Lambda expression, shared pointer and the type of this


Consider the following code:

#include <memory>
#include <cassert>

struct S: std::enable_shared_from_this<S> {
protected:
    S() = default;

    int bar() { return 42; }
};

struct T: S {
    static std::shared_ptr<T> create() {
        return std::shared_ptr<T>(new T{});
    }

    auto foo() {
        auto ptr = std::static_pointer_cast<T>(shared_from_this());
        return [ptr](){ return ptr->bar(); };
    }

private:
    T() = default;
};

int main() {
    std::shared_ptr<T> ptr = T::create();
    auto lambda = ptr->foo();
    assert(lambda() == 42);
}

The code above compiles. It does not if the method foo is modified as it follows:

auto foo() {
    // In this case, there is no explicit cast
    // The type of ptr is no longer std::shared_ptr<T>
    // It is std::shared_ptr<S> instead 
    auto ptr = shared_from_this();
    return [ptr](){ return ptr->bar(); };
}

In this case, the code no longer compiles (neither with GCC nor with clang).

Obviously it would compile after a cast (that is what I did in the first example), but I expected bar to be visible to the lambda even in this case, for it is reachable in its context and part of the interface of S as well.

I suspect that it is due to 5.1.5p8, in particular:

The lambda-expression's compound-statement yields the function-body [...] of the function call operator, but for [...], determining the type and value of this [...], the compound-statement is considered in the context of the lambda-expression.

In fact, the error returned by clang is quite clear:

main.cpp:8:9: note: can only access this member on an object of type T

Is my deduction right?
Is it due to the mentioned paragraph, thus to a problem of determined type for the this pointer that does not match with the one of the shared pointer?

The fact that a shared_ptr takes its part in the game makes it a bit harder to me to understand.
Honestly, I'd expect both the examples would compile or both would fail.


Solution

  • It looks like you are simply violating the basic rules of protected access. I.e. the whole thing has nothing to do with lambdas or shared pointers. The good-old rules of protected access that have been around since the beginning of times say that protected members of base class are only accessible through objects of derived class. Contrary to what you stated above, in your context protected members of S are not accessible through objects of type S, but they are accessible through objects of type T.

    The whole thing can be reduced the the following simple example

    struct S
    {
    protected:
      int i;
    };
    
    struct T : S
    {
        void foo()
        {
            this->i = 5; // OK, access through `T`
    
            T t;
            t.i = 5; // OK, access through `T`
    
            S s;
            s.i = 5; // ERROR: access through `S`, inaccessible
    
            S *ps = this;
            ps->i = 5; // ERROR: access through `S`, inaccessible
        }
    };