这是对"是否有任何实际使用decltype(auto)变量的情况?"的跟进。
考虑以下场景 - 我想将函数f
传递给另一个函数invoke_log_return
,该函数将:
调用
f
;向stdout打印一些内容;
返回
f
的结果,避免不必要的复制/移动并允许复制省略。
请注意,如果f
引发异常,则不应向stdout打印任何内容。这是我目前的代码:
template <typename F>
decltype(auto) invoke_log_return(F&& f)
{
decltype(auto) result{std::forward<F>(f)()};
std::printf(" ...logging here...\n");
if constexpr(std::is_reference_v<decltype(result)>)
{
return decltype(result)(result);
}
else
{
return result;
}
}
让我们考虑不同的可能性:
当
f
返回一个prvalue时:result
将是一个对象;invoke_log_return(f)
将是一个prvalue(有资格进行拷贝省略)。
当
f
返回一个lvalue或xvalue时:result
将是一个引用;invoke_log_return(f)
将是一个lvalue或xvalue。
您可以在godbolt.org上查看测试应用程序。如您所见,g++
对于prvalue情况执行NRVO,而clang++
则不执行。
问题:
这是“完美地”返回
decltype(auto)
变量的最短可能方式吗? 是否有更简单的方法来实现我想要的功能?是否可以将
if constexpr { ... } else { ... }
模式提取到单独的函数中? 提取的唯一方法似乎是使用宏。clang++
没有在上述prvalue情况下执行NRVO是否有任何充分的理由? 应该将其报告为潜在的增强,还是g++
的NRVO优化在这里不合法?
这里是使用on_scope_success
辅助工具的替代方案(如Barry Revzin所建议的):
template <typename F>
struct on_scope_success : F
{
int _uncaught{std::uncaught_exceptions()};
on_scope_success(F&& f) : F{std::forward<F>(f)} { }
~on_scope_success()
{
if(_uncaught == std::uncaught_exceptions()) {
(*this)();
}
}
};
template <typename F>
decltype(auto) invoke_log_return_scope(F&& f)
{
on_scope_success _{[]{ std::printf(" ...logging here...\n"); }};
return std::forward<F>(f)();
}
invoke_log_return_scope
虽然更短,但需要不同的函数行为和新抽象实现的思维模型。令人惊讶的是,g++
和clang++
都使用此解决方案执行RVO/复制消除。
如Ben Voigt所述,这种方法的一个主要缺点是f
的返回值无法成为日志消息的一部分。
on_scope_success
也可以处理空返回类型。 - Jarod42return (result);
总是返回一个lvalue,在f
产生xvalue的情况下执行复制而不是移动。 - Vittorio Romeo