第一题

1
auto* p = +[] {return 6; };

这里的 + 是什么作用?
这是一个非捕获的 lambda,自然可以生成对应转换函数转换为函数指针,这里的一元 + 是为了辅助推导,是为了创造合适的语境。自然理解为使用转换函数返回函数指针,+ 指针,符合规范

这里 auto** 可以去掉

第二题

1
2
3
4
5
6
7
int main() {
static int a = 42;
auto p = [=] { ++a; } ;
std::cout << sizeof p << '\n';
p();
return a;
}

提问,打印p是多少?return a是多少?

答案: 1 43

解释:

  1. 如果变量满足下列条件,那么 lambda 表达式在使用它前不需要先捕获:该变量是非局部变量,或具有静态或线程局部存储期(此时无法捕获该变量),或者该变量是以常量表达式初始化的引用。

  2. 这里的捕获是 [=] ,但是其实写不写都无所谓,反正这个作用域就一个静态局部变量 a,你也无法捕获到这个变量。那么按照空类,p的大小一般来说自然也就是1了。

第三题

1
2
3
4
5
6
int main() {
float x;
float& r = x;
auto p = [=] {};
std::cout << sizeof p << '\n';
}

上面的代码会输出什么? 1

解释:
如果捕获符列表具有默认捕获符,且未显式(以 this 或 *this)捕获它的外围对象,或任何在 lambda 体内可 [ODR 使用]的自动变量,或对应变量拥有自动存储期的结构化绑定 (C++20 起),那么在以下情况下,它隐式捕获之:

  • lambda 体 ODR 使用了该实体

    或者,该实体在取决于某个泛型 lambda 形参的 (C++17 前)表达式内的潜在求值表达式中被指名(包括在使用非静态类成员的前添加隐含的 this->)。就此目的而言,始终认为 typeid 的操作数被潜在求值。即使实体仅在舍弃语句中被指名,它也可能会被隐式捕获。 (C++17 起)

  • 潜在求值

    或者,该实体在取决于某个泛型 lambda 形参的 (C++17 前)表达式内的潜在求值表达式中被指名(包括在使用非静态类成员的前添加隐含的 this->)。就此目的而言,始终认为 typeid 的操作数被潜在求值。即使实体仅在舍弃语句中被指名,它也可能会被隐式捕获。 (C++17 起)

  • typeid

    或者,该实体在取决于某个泛型 lambda 形参的 (C++17 前)表达式内的潜在求值表达式中被指名(包括在使用非静态类成员的前添加隐含的 this->)。就此目的而言,始终认为 typeid 的操作数被潜在求值。即使实体仅在舍弃语句中被指名,它也可能会被隐式捕获。 (C++17 起)

  • 舍弃语句

    或者,该实体在取决于某个泛型 lambda 形参的 (C++17 前)表达式内的潜在求值表达式中被指名(包括在使用非静态类成员的前添加隐含的 this->)。就此目的而言,始终认为 typeid 的操作数被潜在求值。即使实体仅在舍弃语句中被指名,它也可能会被隐式捕获。 (C++17 起)

第四题

1
2
3
4
5
6
7
int main() {
const int N = 10;
auto p =[=] {
int arr[N]{};
};
std::cout << sizeof p << '\n';
}

请问打印多少?1

解释:

  1. 如果变量满足下列条件,那么 lambda 表达式在读取它的值前不需要先捕获: 该变量具有 const 而非 volatile 的整型或枚举类型,并已经用常量表达式初始化,或者 该变量是 constexpr 的且没有 mutable 成员。(这是表示即使不使用=捕获在这里的语境下直接用也没问题)

  2. N没有被ODR使用,没有被捕获

第五题

1
2
3
4
5
6
7
int main() {
const int N = 10;
auto p =[=] {
int p = N;
};
std::cout << sizeof p << '\n';
}

请问打印多少?
msvc:4,gcc:1,clang:1

解释:

1.这属于编译器优化的行为,因为就算不带上 [=],也能正常访问

2.读取编译时常量的值也不是ODR使用。

第六题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void f(int, const int(&)[2] = {}) {}   // #1
void f(const int&, const int(&)[1]) {} // #2

void test()
{
const int x = 17;

auto g0 = [](auto a) { f(x); }; // OK:调用 #1,不捕获 x
std::cout << sizeof g0 << '\n';

auto g1 = [=](auto a) { f(x); }; // C++14 中不捕获 x,C++17 中捕获 x
// 捕获能被优化掉
std::cout << sizeof g1 << '\n';


auto g2 = [=](auto a)
{
int selector[sizeof(a) == 1 ? 1 : 2] = {};
f(x, selector); // OK:这是待决表达式,因此 x 被捕获
};

std::cout << sizeof g2 << '\n';

auto g3 = [=](auto a)
{
typeid(a + x); // 捕获 x,不管 a + x 是否为不求值操作数
};
std::cout << sizeof g3 << '\n';

}

这段代码的运行结果是什么?msvc 和 gcc12.1 都是 1 4 4 4

解释:

  1. 该实体在取决于某个泛型 lambda 形参的(C++17 前)表达式内的潜在求值表达式中被指名(包括在使用非静态类成员的前添加隐含的this->)。就此目的而言,始终认为typeid的操作数被潜在求值。即使实体仅在舍弃语句中被指名,它也可能会被隐式捕获。(C++17 起)

  2. 先复习一下第四题的解释,我这里的代码全部都是[=]捕获的,你就算全部去掉,他们运行的结果是四个1,这也没任何问题,也符合先前说的规则

再举一个简单的例子:

1
2
3
4
5
6
7
8
9
void f(int){}

int main() {
const int N = 10;
auto p = [=] (auto a) {
f(N);
};
std::cout << sizeof p << '\n';
}

gcc和msvc都打印4,这不是重点,当我们把(auto a)这个形参去除了,gcc就打印1。跳转

我们脑补一下,最初gcc认为N没有被ODR使用(其实我也是这么认为的),但是判断是否捕获还有另一个规则,也就是我们解释的第一点那个泛型lambda,所以捕获了, 打印4,如果去除(auto a)的话就是1了

第七题

1
2
3
4
int main() {
auto p =[=]()noexcept {};
std::cout << std::boolalpha << noexcept(p()) << '\n';
}

打印多少?true

解释:

lambda 表达式上的异常说明异常说明应用于函数调用运算符或运算符模板。

第八题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//1.
int main() {
const int a = 6;
[] {
std::cout << a << '\n';
}();
}//通过编译

//2.
int main() {
constexpr int a = 6;
[] {
std::cout << a << '\n';
}();
}//通过编译

//3.
int main() {
const float a = 6;
[] {
std::cout << a << '\n';
}();
}//error

//4.
int main() {
constexpr float a = 6;
[] {
std::cout << a << '\n';
}();
}//通过编译

//5.
int main() {
const int a = 6;
[] {
std::cout << &a << '\n';
}();
}//error
  1. 如果变量满足下列条件,那么 lambda 表达式在读取它的值前不需要先捕获:
    该变量具有 const 而非 volatile 的整型或枚举类型,并已经用常量表达式初始化,或者
    该变量是 constexpr 的且没有 mutable 成员。
  2. 同上
  3. 同上
  4. 同上
  5. 这里需要特别说明一下,是读取值不需要先捕获,不是和第二题那种情况一样,可以直接使用,要区分,如果你改成 [=] 的话也就合法了