[英]C++ Lambdas, Capturing, Smart Ptrs, and the Stack: Why Does this Work?
[英]Why does capturing stateless lambdas sometimes results in increased size?
给定一个 lambda 链,其中每个都通过值捕获前一个:
auto l1 = [](int a, int b) { std::cout << a << ' ' << b << '\n'; };
auto l2 = [=](int a, int b) { std::cout << a << '-' << b << '\n'; l1(a, b); };
auto l3 = [=](int a, int b) { std::cout << a << '#' << b << '\n'; l2(a, b); };
auto l4 = [=](int a, int b) { std::cout << a << '%' << b << '\n'; l3(a, b); };
std::cout << sizeof(l4);
我们可以观察到,得到的l4
的sizeof
等于1
。
这对我来说很有意义。 我们按值捕获 lambda,每个对象的sizeof
必须等于1
,但由于它们是无状态的,因此适用类似于[[no_unique_address]]
的优化(特别是因为它们都有唯一的类型)。
但是,当我尝试为链接比较器创建通用构建器时,不再发生这种优化:
template <typename Comparator>
auto comparing_by(Comparator&& comparator) {
return comparator;
}
template <typename Comparator, typename... Comparators>
auto comparing_by(Comparator&& comparator, Comparators&&... remaining_comparators) {
return [=](auto left, auto right) {
auto const less = comparator(left, right);
auto const greater = comparator(right, left);
if (!less && !greater) {
return comparing_by(remaining_comparators...)(left, right);
}
return less;
};
}
struct triple {
int x, y, z;
};
auto main() -> int {
auto by_x = [](triple left, triple right) { return left.x < right.x; };
auto by_y = [](triple left, triple right) { return left.y < right.y; };
auto by_z = [](triple left, triple right) { return left.z < right.z; };
auto comparator = comparing_by(by_x, by_z, by_y);
std::cout << sizeof(comparator);
}
注 1:我知道 compare_by 效率低下,有时以冗余方式调用comparing_by
器。
为什么在上述情况下, comparator
的结果sizeof
等于3
而不是1
? 毕竟,它仍然是无国籍的。 我哪里错了? 或者它只是所有三大编译器中错过的优化?
注2:这纯粹是一个学术问题。 我不是想解决任何特定的问题。
第一个例子中发生的事情不是你想象的那样。 假设l1
具有类型L1
、 l2
L2
等。这些是这些类型的成员:
struct L1 {
// empty;
};
sizeof(L1) == 1
struct L2 {
L1 l1;
};
sizeof(L2) == sizeof(L1) // 1
struct L3 {
L2 l2;
};
sizeof(L3) == sizeof(L2) // 1
struct L4 {
L3 l3;
};
sizeof(L4) == sizeof(L3) // 1
在您的下一个示例中,您按值捕获所有 lambda,因此闭包类型具有三个不重叠的成员,因此大小至少为 3。
[[no_unique_address]]
通常不能应用于闭包类型的数据成员(考虑一个空的 class 将其地址放在全局映射中)。
编译器可以对“行为良好的类型”(可能是可简单复制的空类型?)使用空基优化,因此这可能是一个错过的优化。 该标准说明了可以做什么([expr.prim.lambda.closure]p2):
闭包类型不是聚合类型。 一个实现可以定义不同于下面描述的闭包类型,前提是这不会改变程序的可观察行为,除非改变:
- 闭合类型的尺寸和/或 alignment,
- 闭包类型是否可以简单复制([class.prop]),或者
- 闭包类型是否为标准布局 class ([class.prop])。
所以大小的变化是可以的,但必须这样做,以便is_empty_v<lambda_that_captures_stateless_lambda>
不是true
(因为这是一个可观察的行为)
要“手动”应用此优化,您可以不调用 lambda comparator(left, right)
,而是默认构造闭包类型的类型并调用它( decltype(comparator){}(left, right)
)。 我在这里实现了: https://godbolt.org/z/73M1Gd3o5
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.