cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[class.access.base] p5 Whether the body of an instantiated call operator function of a lambda is within the accessible point is underspecified. #395

Closed xmh0511 closed 1 year ago

xmh0511 commented 1 year ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

Consider this example:

class A{
    int a;
    public:
    auto show(){
        auto f = [](auto t){
           auto c = t.a;
        };
        return f;
    }
};
int main(){
    auto f = A{}.show();
    f(A{});  // #1
}

[class.access.base] p5 says:

A member m is accessible at the point R when named in class N if

  • [...]
  • m as a member of N is private, and R occurs in a direct member or friend of class N, or

The lambda-expression appears in the member of A, however, t is a generic parameter type(i.e. dependent type), and its type is determined when the specialization is instantiated for the calling at #1. [expr.prim.lambda] just says

The closure type for a lambda-expression has a public inline function call operator (for a non-generic lambda) or function call operator template (for a generic lambda)...

The lambda-expression's compound-statement yields the function-body ([dcl.fct.def]) of the function call operator, but it is not within the scope of the closure type.

It is not clear whether the specializations of the function call operator template for the genric lambda are considered within the member of A such that any specialization can access the private member of A.

Suggested Resolution

We may insert a phrase in [expr.prim.lambda] to say:

For the purpose of access checking, the specialization of the function call operator template has the same access as that of the lambda-expression.

frederick-vs-ja commented 1 year ago

I think the phrase "occurs in" should be interpreted lexically and thus shouldn't be affected by instantiation.

xmh0511 commented 1 year ago

I think the phrase "occurs in" should be interpreted lexically and thus shouldn't be affected by instantiation.

In this case, the access checking cannot lexically apply to t.a since t has a dependent type, further name lookup will happen in the instantiation context, hence access checking should also happen in that context, which is after name lookup.

jensmaurer commented 1 year ago

What happens for a function template defined inside a class with a dependent parameter possibly taking the class of which the function template is a member? I think the situation is (or should be) exactly the same.

xmh0511 commented 1 year ago

What happens for a function template defined inside a class with a dependent parameter possibly taking the class of which the function template is a member? I think the situation is (or should be) exactly the same.

Because the function template is a member of that class, all of its specializations are members of that class, hence they have permission to access the private member. Instead, lambda-expression is not a member of that class. Furthermore, consider this example:

template<class T>
class B{
    int b;
public:
    void fun(B<int> obj){
        auto c = obj.b;
    }
};
int main(){
    B<char> c;
    c.fun(B<int>{});
}

All implementations render an error:

error: 'b' is a private member of 'B'

note: in instantiation of member function 'B::fun' requested here

This is sufficient to say void B<char>::fun is not a member of B<int>(which error happens in the instantiation context), it cannot access the private member of B<int>, which case is an analogy to the lambda-expression(i.e. closure type).

jensmaurer commented 1 year ago

[expr.prim.lambda.closure] p2 says

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression.

If we had a local class inside a member function, I think the members of the local class would also have access. Why should lambdas be different?

xmh0511 commented 1 year ago

If we had a local class inside a member function, I think the members of the local class would also have access. Why should lambdas be different?

Ah, you're right. [class.access.general] p2 explicitly mentions this case:

A member of a class can also access all the members to which the class has access. A local class of a member function may access the same members that the member function itself may access.