我试图围绕无状态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?
答案 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()()
的空结构。
所有这些都是答案为什么起作用。