从函数正确传播`decltype(auto)`变量

Vit*_*meo 24 c++ copy-elision auto c++17 decltype-auto

(这是“ 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们考虑各种可能性:

  • f返回prvalue时

    • result 将成为一个对象;

    • invoke_log_return(f)将是一个prvalue(有资格使用复制省略)。

  • f返回左值x值

    • result 将作为参考;

    • invoke_log_return(f)将是一个左值x

您可以在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)();
}
Run Code Online (Sandbox Code Playgroud)

虽然invoke_log_return_scope要短得多,但这需要对功能行为和新抽象的实现使用不同的思维模型。出人意料的是,两者g++clang++用此溶液进行RVO /复印,省音。

Godbolt.org上的实时示例

正如Ben Voigt所提到的,这种方法的一个主要缺点是的返回值f不能成为日志消息的一部分。

L. *_* F. 3

我们可以使用修改后的版本std::forward:(避免名称转发以防止 ADL 问题)

template <typename T>
T my_forward(std::remove_reference_t<T>& arg)
{
    return std::forward<T>(arg);
}
Run Code Online (Sandbox Code Playgroud)

该函数模板用于转发decltype(auto)变量。它可以这样使用:

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);
}
Run Code Online (Sandbox Code Playgroud)

这样,如果std::forward<F>(f)()返回

  • 纯右值,则为result非引用,并invoke_log_return返回非引用类型;

  • 一个左值,那么result是一个左值引用,并invoke_log_return返回一个左值引用类型;

  • xvalue,则为result右值引用,并invoke_log_return返回右值引用类型。

(基本上是从我的/sf/answers/4020857011/复制的)

  • 这不允许复制省略。return 语句的操作数不是命名函数局部变量的 id 表达式。所以这是一种悲观情绪。这也是一种相当冗长的执行更简单的无条件“return decltype(result)(result);”的方法 (3认同)