从函数正确传播`decltype(auto)`变量 [英] Correctly propagating a `decltype(auto)` variable from a function

查看:110
本文介绍了从函数正确传播`decltype(auto)`变量的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(这是"" )

请考虑以下情形-我要将一个函数f传递给另一个函数invoke_log_return,该函数将:

  1. 调用f;

  2. 打印一些内容到 stdout ;

  3. 返回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/复制删除.

godbolt.org上的实时示例

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:

  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.

解决方案

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)`变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆