问题描述
(这是"" )
请考虑以下情形-我要将一个函数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 所述,此方法的主要缺点是返回值不能成为日志消息的一部分.
这是最简单,最清晰的书写方式:
template <typename F>
auto invoke_log_return(F&& f)
{
auto result = f();
std::printf(" ...logging here... %s\n", result.foo());
return result;
}
海湾合作委员会得到了权利(没有不必要的副本或移动)预期结果:
s()
in main
prvalue
s()
...logging here... Foo!
lvalue
s(const s&)
...logging here... Foo!
xvalue
s(s&&)
...logging here... Foo!
因此,如果代码很清晰,则具有相同的功能,但没有经过优化,无法像竞争对手那样运行,这是编译器优化失败,应该由clang解决.这种问题在工具而不是应用程序层实现中更有意义.
https://gcc.godbolt.org/z/50u-hT
(This is a follow-up from "Are there any realistic use cases for `decltype(auto)` variables?")
Consider the following scenario - I want to pass a function f
to another function invoke_log_return
which will:
Invoke
f
;Print something to stdout;
Return the result of
f
, avoiding unnecessary copies/moves and allowing copy elision.
Note that, if f
throws, nothing should be printed to stdout. This is what I have so far:
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;
}
}
Let's consider the various possibilities:
When
f
returns a prvalue:result
will be an object;invoke_log_return(f)
will be a prvalue (eligible for copy elision).
When
f
returns an lvalue or xvalue:result
will be a reference;invoke_log_return(f)
will be a lvalue or xvalue.
You can see a test application here on godbolt.org. As you can see, g++
performs NRVO for the prvalue case, while clang++
doesn't.
Questions:
Is this the shortest possible way of "perfectly" returning a
decltype(auto)
variable out of a function? Is there a simpler way to achieve what I want?Can the
if constexpr { ... } else { ... }
pattern be extracted to a separate function? The only way to extract it seems to be a macro.Is there any good reason why
clang++
does not perform NRVO for the prvalue case above? Should it be reported as a potential enhancement, or isg++
's NRVO optimization not legal here?
Here's an alternative using a on_scope_success
helper (as suggested by 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)();
}
While invoke_log_return_scope
is much shorter, this requires a different mental model of the function behavior and the implementation of a new abstraction. Surprisingly, both g++
and clang++
perform RVO/copy-elision with this solution.
One major drawback of this approach, as mentioned by Ben Voigt, is that the return value of f
cannot be part of the log message.
That's the simplest and most clear way to write it:
template <typename F>
auto invoke_log_return(F&& f)
{
auto result = f();
std::printf(" ...logging here... %s\n", result.foo());
return result;
}
The GCC gets the right (no needless copies or moves) expected result:
s()
in main
prvalue
s()
...logging here... Foo!
lvalue
s(const s&)
...logging here... Foo!
xvalue
s(s&&)
...logging here... Foo!
So if code is clear, have ever the same functionality but is't optimized to run as much as the competitors does it's a compiler optimization failure and clang should work it out. That's the kind of problem that make lot more sense solved in the tool instead the application layer implementation.
https://gcc.godbolt.org/z/50u-hT
这篇关于从函数正确传播`decltype(auto)`变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!