处于默认参数位置的 Lambda 无法访问好友成员。这是编译器错误吗?

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

本文关键字:成员 好友 访问 错误 编译器 默认 于默认 参数 Lambda 位置      更新时间:2023-10-16

我正在尝试编译一个用超现代神秘编码技术编写的程序。这些技术非常先进,GCC和Clang可以工作,但Visual Studio 2017会抛出错误。现在我想知道Visual Studio是否正确。

请考虑以下程序:

#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 抛出错误 C2248:"A::foo":无法访问在类"A"中声明的私有成员。

错误的根源在于 printFooFromA 的声明:"printer"参数以 lambda 的形式给出默认值。在 lambda 内部,它访问 A::foo。由于 foo 是私有的,因此只能在 A 或 A 的朋友内部访问。

这是否是一个错误取决于lambda是否应该被视为A的朋友。C++标准是否对此进行了规定?

编辑添加:StackOverflow 上存在关于 lambda 是否被视为类的朋友的问题,但这些问题都没有解决 lambda 处于默认参数位置的情况以及这是否符合标准C++。

根据C++17 [class.friend]/2

将类声明为好友意味着可以在好友类的基本说明符和成员声明中访问授予友谊的类中的私有和受保护成员的名称。

声明类成员函数当然是成员声明,并且 lambda 肯定在声明中,所以我会将其解释为表示 lambda 具有友谊状态。

为了支持这一点,有[expr.prim.lambda.closure]/2:

闭包类型在包含相应 lambda 表达式的最小块作用域、类作用域或命名空间作用域中声明。[注意:这决定了与闭包类型关联的命名空间和类集。

所以lambda的闭包类型在B的范围内声明,这意味着B大致等价于:

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);
}
};

在 [class.friend]/2 中,它给出了一个具有相同布局的示例,表明friend class X;意味着嵌套的X类也是友元。

很明显,这是MSVC中的一个错误。