问题
(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
freturns a prvalue:resultwill be an object;invoke_log_return(f)will be a prvalue (eligible for copy elision).
When
freturns an lvalue or xvalue:resultwill 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.
live example on godbolt.org
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.
回答1:
We can use a modified version of std::forward: (the name forward is avoided to prevent ADL problems)
template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
return std::forward<T>(arg);
}
This function template is used to forward a decltype(auto) variable. It can be used like this:
template <typename F>
decltype(auto) invoke_log_return(F&& f)
{
decltype(auto) result{std::forward<F>(f)()};
std::printf(" ...logging here...\n");
return my_forward<decltype(result)>(result);
}
This way, if std::forward<F>(f)() returns
a prvalue, then
resultis a non-reference, andinvoke_log_returnreturns a non-reference type;an lvalue, then
resultis an lvalue-reference, andinvoke_log_returnreturns an lvalue reference type;an xvalue, then
resultis an rvalue-reference, andinvoke_log_returnreturns an rvalue reference type.
(Essentially copied from my https://stackoverflow.com/a/57440814)
回答2:
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
来源:https://stackoverflow.com/questions/57444893/correctly-propagating-a-decltypeauto-variable-from-a-function