Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda in default argument position can't access friend members. Is this a compiler bug?

I'm trying to compile a program which was written with super modern arcane coding techniques. These techniques are so advanced that GCC and Clang work but Visual Studio 2017 throws an error. Now I'm wondering whether Visual Studio got it right.

Consider the following program:

#include <functional>
#include <iostream>

class A
{
public:
    A(int i) : foo(i) { }
private:
    int foo;

    friend class B;
};

class B
{
public:
    void printFooFromA(const A& a, std::function<void (const A&)> printer = [](const A& a) {
        std::cout << "a.foo is " << a.foo << std::endl;
    }) {
        printer(a);
    }
};

int main()
{
    A a(123);
    B b;
    b.printFooFromA(a);
    return 0;
}

Visual Studio throws Error C2248:'A::foo': cannot access private member declared in class 'A'.

The root of the error is in the declaration for printFooFromA: The "printer" argument is given a default value in the form of a lambda. Inside the lambda, it accesses A::foo. Since foo is private, it can only be accessed within A or a friend of A.

Whether this is an error or not hinges on if the lambda should be considered a friend of A. Visual Studio says no while GCC says yes. Does the C++ standard specify this?

Edited to add: There are existing questions on StackOverflow concerning whether a lambda is considered a friend of a class, but none of those questions address the case where a lambda is in default argument position and whether this complies with standard C++.

like image 461
N.E.C. Avatar asked Jun 18 '18 02:06

N.E.C.


1 Answers

According to C++17 [class.friend]/2:

Declaring a class to be a friend implies that the names of private and protected members from the class granting friendship can be accessed in the base-specifiers and member declarations of the befriended class.

Declaring a class member function is certainly a member declaration, and the lambda is certainly in the declaration, so I would interpret this as saying that the lambda has friendship status.

To back this up, there is [expr.prim.lambda.closure]/2:

The closure type is declared in the smallest block scope, class scope, or namespace scope that contains the corresponding lambda-expression. [Note: This determines the set of namespaces and classes associated with the closure type.]

So the closure type of the lambda is declared in B's scope, meaning that B is roughly equivalent to:

class B
{
    struct lam
    {
        void operator()(const A& a)
        {
            std::cout << "a.foo is " << a.foo << std::endl;
        }
    };
public:
    void printFooFromA(const A& a, std::function<void (const A&)> printer = lam()) 
    {
        printer(a);
    }
};

In [class.friend]/2 it gives an example with this same layout, showing that friend class X; means that nested classes of X are also friends.

It seems clear that this is therefore a bug in MSVC.

like image 156
M.M Avatar answered Sep 18 '22 00:09

M.M