如何在C++14及以上版本中实现类的noexcept lambda类函数指针成员?
尝试将类的函数指针成员赋值为noexcept lambda,同时尝试用std::function包装noexcept函数类型时,遇到跨版本、跨编译器的编译错误。代码示例:
#include <functional> #include <iostream> #define USEFUNPTR #define USESTDFUN class Dummy { public: #ifdef USEFUNPTR void (*Foo)() noexcept = nullptr; #endif #ifdef USESTDFUN std::function<void() noexcept> Bar; #endif void InitFuns() { #ifdef USEFUNPTR Foo = []() noexcept { std::cout << "Foo\n" << std::endl; }; #endif #ifdef USESTDFUN Bar = []() noexcept { std::cout << "Bar\n" << std::endl; }; #endif }; ~Dummy() noexcept = default; };
错误原因分析
1. 函数指针版本(USEFUNPTR)
- C++14环境下MSVC报错:
error C2440: '=': cannot convert from 'Dummy::InitFuns::<lambda_b40e7171393910f4bba39b7be19bf362>' to 'void (__cdecl *)(void) noexcept'
C14标准中,noexcept不属于函数类型的一部分,因此void (*Foo)() noexcept是编译器扩展特性(GCC支持但MSVC在C14模式下不兼容)。Lambda隐式转换生成的函数指针不包含noexcept属性,与声明的带noexcept的函数指针类型不匹配,导致转换失败。 - C++17及以后:
noexcept正式成为函数类型的一部分,但部分编译器对Lambda隐式转换为带noexcept的函数指针支持有限,可能需要显式转换才能通过编译。
2. std::function版本(USESTDFUN)
C++17开始noexcept被纳入函数类型,但标准库的std::function并没有提供接受带noexcept函数类型的特化实现,因此所有编译器都会报错:
- GCC报错:
error: field 'Bar' has incomplete type 'std::function<void() noexcept>'
- MSVC报错:
error C2338: static_assert failed: 'std::function does not accept noexcept function types as template arguments.'
error C2440: '=': cannot convert from 'Dummy::InitFuns::<lambda_b40e7171393910f4bba39b7be19bf362>' to 'void (__cdecl *)(void) noexcept'
本质原因是std::function<R(Args...)>的调用运算符固定为noexcept(false),无法匹配带noexcept的函数类型模板参数。
跨版本兼容解决方案
方案一:使用无noexcept标注的函数指针/std::function
这是最兼容C++14及以上所有编译器的方案:
- 对于函数指针成员,移除
noexcept标注,保持Lambda的noexcept属性:class Dummy { public: void (*Foo)() = nullptr; // 移除noexcept标注 void InitFuns() { // Lambda的noexcept属性会被保留,调用时不会抛出异常 Foo = []() noexcept { std::cout << "Foo\n" << std::endl; }; } }; - 对于
std::function成员,移除模板参数中的noexcept:
虽然class Dummy { public: std::function<void()> Bar; // 移除noexcept标注 void InitFuns() { // noexcept Lambda可以正常赋值给std::function<void()> Bar = []() noexcept { std::cout << "Bar\n" << std::endl; }; } };std::function<void()>的operator()本身不是noexcept,但实际调用的Lambda不会抛出异常,安全性可以保证。如果需要在调用站点明确noexcept,可以手动添加:void CallBar(const Dummy& d) noexcept { if (d.Bar) d.Bar(); }
方案二:显式转换适配C++17的带noexcept函数指针
如果必须保留函数指针的noexcept标注,可以通过版本宏做条件编译:
class Dummy { public: #ifdef __cpp_noexcept_function_type // C++17及以后支持noexcept函数类型 void (*Foo)() noexcept = nullptr; #else void (*Foo)() = nullptr; #endif void InitFuns() { #ifdef __cpp_noexcept_function_type // 显式转换为带noexcept的函数指针 Foo = static_cast<void(*)() noexcept>([]() noexcept { std::cout << "Foo\n" << std::endl; }); #else Foo = []() noexcept { std::cout << "Foo\n" << std::endl; }; #endif } };
这种方式可以在C17及以后保留noexcept的类型标注,C14下退化为普通函数指针,保证跨版本兼容。
Lambda转函数指针的疑问解答
Lambda无法隐式转换为带noexcept的函数指针的核心原因:
- C++14中,
noexcept不属于函数类型的一部分,因此不存在“带noexcept的函数指针”这一标准类型,Lambda自然无法隐式转换为非标准的扩展类型。 - C++17中,虽然
noexcept成为函数类型的一部分,但Lambda的隐式转换规则仅允许转换为与自身operator()属性完全匹配的函数指针。部分编译器对这种隐式转换的支持不完善,因此需要显式static_cast来完成转换。
内容的提问来源于stack exchange,提问作者Oersted

