lambda捕获的"空基础优化" - 标准禁止?为什么?

Vit*_*meo 9 c++ lambda c++17

我最近遇到了一种情况,我最终得到了大量嵌套的lambdas来构建异步计算链.

template <typename F>
struct node : F
{
    node(F&& f) : F{std::move(f)}
    {
    }

    template <typename FThen>
    auto then(FThen&& f_then)
    {
        return ::node{[p = std::move(*this), t = std::move(f_then)]()
        {   
        }};
    }
};

int main()
{
    auto f = node{[]{ }}.then([]{ }).then([]{ });
    return sizeof(f);
}   
Run Code Online (Sandbox Code Playgroud)

我在lambda中捕获的所有对象都是空的,但最终对象的大小大于1:gcc.godbolt.org上的示例.

如果我将lambda内部更改为node</* ... */>::then具有显式EBO的函数对象,则最终对象的大小将变为1.

template <typename P, typename T>
struct node_lambda : P, T
{
    node_lambda(P&& p, T&& t) : P{std::move(p)}, T{std::move(t)}
    {
    }

    void operator()()
    {
    }
};
Run Code Online (Sandbox Code Playgroud)

template <typename FThen>
auto node</* ... */>::then(FThen&& f_then)
{
    return ::node{node_lambda{std::move(*this), std::move(f_then)}};
}
Run Code Online (Sandbox Code Playgroud)

gcc.godbolt.org上的实例


我发现这真的很烦人,因为我被迫:

  • 写了很多样板代码,大致相当于lambda.

  • 由于像EBO这样的东西不适用于lambda捕获,因此需要支付额外的内存成本.

标准中是否存在明确强制空lambda捕获以获取额外空间的任何内容?如果是这样,为什么?

Bar*_*rry 10

来自expr.prim.lambda.capture:

对于由副本捕获的每个实体,在闭包类型中声明未命名的非静态数据成员.

虽然这里的lambdas没有捕获:

auto f = node{[]{ }}.then([]{ }).then([]{ });
Run Code Online (Sandbox Code Playgroud)

因此没有未命名的非静态数据成员,因此是空的,这不是then()实际使用的.它使用这个:

return ::node{[p = std::move(*this), t = std::move(f_then)](){}};
Run Code Online (Sandbox Code Playgroud)

拉姆达捕获tp通过拷贝,因此有两个未命名的非静态数据成员.每个都.then()添加另一个成员变量,即使每个变量都是空的,因此节点的大小也会不断增加.

或者换句话说,空基本优化仅适用于基础,而lambdas的捕获不会创建基础,它会创建非静态数据成员.

  • @vittorio允许具有不同类型(并且没有公共基础或前缀)的空成员变量来共享内存的某种方式在lambdas之外可能是有用的.如果有一种方法可以通过这种方式告诉类"更紧凑",那么添加一个允许lambdas具有该功能的子句可能比为lambdas添加该功能更清晰...... (2认同)