无状态Lambda作为静态局部变量

时间:2020-10-02 22:37:18

标签: c++ templates lambda c++17

我试图围绕无状态lambda编写模板包装器类。像这样:

template <class TFuncOp>
class Adapter
{
public:
    void Op()
    {
        TFuncOp func; // not possible before C++20
        func();
    }
};

由于在C ++ 20附带默认可构造lambda之前这是不可能的,所以我使用了这种技术来使我的类正常工作:Calling a stateless lambda without an instance (only type)

所以最终的解决方案如下:

template <class TFuncOp>
class Adapter
{
public:
    static TFuncOp GetOpImpl( TFuncOp *pFunc = 0 )
    {
        static TFuncOp func = *pFunc;
        return func;
    }

    void Op()
    {
        GetOpImpl()();
    }
};

template <class TFuncOp>
Adapter<TFuncOp> MakeAdapter(TFuncOp func )
{
    // Removing the line below has no effect.
    //Adapter<TFuncOp>::GetOpImpl( &func );
    return Adapter<TFuncOp>();
}

int main()
{
    auto adapter = MakeAdapter( [] { printf("Hello World !\n"); } );
    adapter.Op();
    return 0;
}

此代码可在所有主要编译器(clang,gcc,msvc)上使用。但是有一个令人惊讶的发现。 GetOpImpl()中lambda静态局部实例的初始化(或缺少初始化)无效。无论哪种方式都可以正常工作。

谁能解释这是如何工作的?如果我使用lambda的静态本地实例而不进行初始化,我是否会调用UB?

1 个答案:

答案 0 :(得分:2)

在任何情况下,访问nullptr都不是一个好主意,因为它是UB。

但是我们可以看到典型的实现会生成简单起作用的代码。我尝试解释原因:

首先,它与lambda无关。根本就不需要在没有数据的类上使用复制构造函数。由于没有数据,因此生成的代码将无法访问传递的对象。在您的情况下,您“复制”了指针TFuncOp *pFunc = 0所指向的对象,它是一个nullptr,如果必须访问该对象,它将崩溃。由于没有数据可访问,因此典型的实现方式将不会生成将完全访问nullptr的任何代码。但是它仍然是UB。

相同的功能以相同的方式与所有其他类型一起使用,而lambda并没有什么特别之处!

struct Empty
{
    void Do() { std::cout << "This works the same way" << std::endl; }
    // int i; // << if you add some data, you get a seg fault
};

int main()
{
    Empty* ptr = nullptr;
    Empty empty = *ptr; // get seg fault here, because default copy constructor access the nullptr, but typically only if copy ctor needs to access!

    empty.Do();
}

没有捕获数据的lambda是带有operator()()的空结构。

所有这些都是答案为什么起作用。

相关问题