如何使call_impl模板函数支持返回lambda调用的引用类型?
在基于PImpl惯用法的代码实现中,尝试通过call_impl函数包装lambda并返回引用类型时,触发了编译错误。原代码如下:
#include <iostream> #include <memory> class FooImp { public: int& C() { return _value; } private: int _value{}; }; class Foo { public: Foo() : _imp{ std::make_unique<FooImp>() } { } int& C() { // In member function 'int& Foo::C()': cannot bind non-const lvalue reference // of type 'int&' to an rvalue of type 'int' return call_impl([&] { return _imp->C(); }); } private: template<typename fn_t> auto call_impl(fn_t fn) -> decltype(fn()) { std::cout << "Construct Measure here\n"; struct OnExit{ ~OnExit() { std::cout << "Construct Log here\n"; } } onExit; return fn(); } std::unique_ptr<FooImp> _imp; }; int main() { Foo foo; std::cout << foo.C() << "\n"; return 0; }
编译错误信息
error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
24 | return call_impl([&] { return _imp->C(); });
| ~~~~~~~~~~~~~~~~~~~^
错误原因
默认情况下,lambda的返回值会被隐式退化为右值:即使_imp->C()返回的是int&类型的左值引用,lambda会返回该引用绑定值的副本(右值),而非原引用。当call_impl返回这个右值时,无法将其绑定到Foo::C()声明的int&返回类型,从而触发编译错误。
可行解决方案
方案1:显式指定lambda的返回类型为引用
修改lambda,通过-> int&显式声明返回引用类型,避免返回值被隐式退化:
int& C() { return call_impl([&]() -> int& { return _imp->C(); }); }
此方案中,lambda会直接返回原引用,call_impl的返回类型也会推导为int&,与外层函数的返回要求匹配。
方案2:使用decltype(auto)优化call_impl的返回类型推导
修改call_impl的返回类型为decltype(auto),该类型会精准推导fn()的返回值类型(包括引用属性),无需修改lambda:
template<typename fn_t> decltype(auto) call_impl(fn_t fn) { std::cout << "Construct Measure here\n"; struct OnExit{ ~OnExit() { std::cout << "Construct Log here\n"; } } onExit; return fn(); }
decltype(auto)会严格保留fn()的返回类型——如果fn()返回引用,call_impl也会返回相同的引用类型,完美适配外层函数的返回要求。
验证结果
采用任意一种方案修改后,代码均可正常编译运行,输出如下:
Construct Measure here 0 Construct Log here
内容的提问来源于stack exchange,提问作者RoQuOTriX

