Correctly propagating a `decltype(auto)` variable from a function

孤街浪徒 提交于 2019-12-20 16:46:40

问题


(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:

  1. Invoke f;

  2. Print something to stdout;

  3. 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 is g++'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 result is a non-reference, and invoke_log_return returns a non-reference type;

  • an lvalue, then result is an lvalue-reference, and invoke_log_return returns an lvalue reference type;

  • an xvalue, then result is an rvalue-reference, and invoke_log_return returns 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!