【发布时间】:2019-12-18 01:42:42
【问题描述】:
(这是“Are there any realistic use cases for `decltype(auto)` variables?”的后续报道)
考虑以下场景 - 我想将函数 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返回一个左值或x值时:result将作为参考;invoke_log_return(f)将是 lvalue 或 xvalue。
您可以看到一个测试应用程序here on 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/copy-elision。
正如Ben Voigt 所提到的,这种方法的一个主要缺点是f 的返回值不能是日志消息的一部分。
【问题讨论】:
-
on_scope_success也处理 void 返回类型。 -
decltype(result)(result) 是不是有点多余?不返回(结果)做同样的事情。返回类型为 decltype(auto) 的函数和带有括号内值的 return 语句会自动返回一个引用。
-
作用域成功变体的缺点是无法记录返回值!
-
对我来说更有趣的问题是:gcc 的 NRVO 是否允许?为什么clang不做呢?
-
@engf-010:
return (result);总是返回一个 lvalue,在f产生 xvalue.
标签: c++ c++17 auto copy-elision decltype-auto