需要将一个调用的输出完美传递给另一个调用吗?

huangapple go评论116阅读模式
英文:

Is forward needed to perfectly pass output of a call to another call?

问题

根据cppreference,有必要使用 std::forward() 来完美地传递一个函数的结果,而不需要在其他地方显式存储它。

[...] 例如,如果一个包装器不只是转发其参数,而是调用参数上的一个成员函数,并转发其结果:

// 转换包装器
template<class T>
void wrapper(T&& arg)
{
    foo(std::forward<decltype(std::forward<T>(arg).get())>(std::forward<T>(arg).get()));
}

其中 arg 的类型可能是:

struct Arg
{
    int i = 1;
    int get() && { return i; } // 对这个重载的调用是 rvalue
    int& get() & { return i; } // 对这个重载的调用是 lvalue
};

试图将 rvalue 转发为 lvalue,例如通过使用 lvalue 引用类型 T 来实例化形式 (2),会导致编译时错误。[...]

难道不是这样吗?调用一个函数,其参数是另一个调用的结果,会完美地传递原始值类型和类别吗?考虑以下与原始示例相似的代码片段:

struct Inner {};

struct Arg
{
    Inner inner{};
    Inner&& get() && { return std::move(inner); } // 对这个重载的调用是 rvalue
    Inner& get() & { return inner; } // 对这个重载的调用是 lvalue
    const Inner& get() const & { return inner; } // 对这个重载的调用也是 lvalue
};

void foo(Inner&&) { std::cout << "&&" << std::endl; }
void foo(Inner&) { std::cout << "&" << std::endl; }
void foo(const Inner&) { std::cout << "const&" << std::endl; }

template<class T>
void wrapper(T&& arg)
{
    // 注意:选择前一个版本或后一个版本!

    // 使用额外的 forward
    foo(std::forward<decltype(std::forward<T>(arg).get())>(std::forward<T>(arg).get()));

    // 不使用额外的 forward
    foo(std::forward<T>(arg).get());
}

int main()
{
    // prvalue
    wrapper(Arg{});
    // xvalue
    Arg arg;
    wrapper(std::move(arg));
    // lvalue
    Arg arg2;
    wrapper(arg2);
    // const lvalue
    const Arg arg3;
    wrapper(arg3);

    return 0;
}

在这两种情况下,输出完全相同:

&&
&&
&
const&
英文:

According to the (cppreference) there is a need to use std::forward() in order to perfectly pass outcome of a function passed to another call without storing it explicitly elsewhere.

> [...] For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:

// transforming wrapper
template&lt;class T&gt;
void wrapper(T&amp;&amp; arg)
{
    foo(forward&lt;decltype(forward&lt;T&gt;(arg).get())&gt;(forward&lt;T&gt;(arg).get()));
}

> where the type of arg may be

struct Arg
{
    int i = 1;
    int  get() &amp;&amp; { return i; } // call to this overload is rvalue
    int&amp; get() &amp;  { return i; } // call to this overload is lvalue
};

> Attempting to forward an rvalue as an lvalue, such as by instantiating the form (2) with lvalue reference type T, is a compile-time error. [...]

Isn't is the case that calling a function with a parameter that is outcome of another call perfectly passes the outcome with original value type and category? Consider following snippet that is close to the original example:

struct Inner{};

struct Arg
{
    Inner inner{};
    Inner&amp;&amp; get() &amp;&amp; { return move(inner); } // call to this overload is rvalue
    Inner&amp; get() &amp; { return inner; } // call to this overload is lvalue
    const Inner&amp; get() const &amp; { return inner; }; // call to this overload is also lvalue
};

void foo(Inner&amp;&amp;) { std::cout &lt;&lt; &quot;&amp;&amp;&quot; &lt;&lt; std::endl; }
void foo(Inner&amp;) { std::cout &lt;&lt; &quot;&amp;&quot; &lt;&lt; std::endl; }
void foo(const Inner&amp;) { std::cout &lt;&lt; &quot;const&amp;&quot; &lt;&lt; std::endl; }

template&lt;class T&gt;
void wrapper(T&amp;&amp; arg)
{
    // NOTE: choose either the former or the latter version!

    // with extra forward
    foo(forward&lt;decltype(forward&lt;T&gt;(arg).get())&gt;(forward&lt;T&gt;(arg).get()));
    
    // without extra forward
    foo(forward&lt;T&gt;(arg).get());
}

int main()
{
    // prvalue
    wrapper(Arg{});
    // xvalue
    Arg arg;
    wrapper(move(arg));
    // lvalue
    Arg arg2;
    wrapper(arg2);
    // const lvalue
    const Arg arg3;
    wrapper(arg3);
    
    return 0;
}

In both cases the output is exactly the same:

&amp;&amp;
&amp;&amp;
&amp;
const&amp;

答案1

得分: 1

你说得对。第二个forward除了强制返回值函数返回一个 prvalue 以创建一个临时值(通常是不必要的)之外,什么都不做。

如果 decltype(forward<T>(arg).get()) 是左值引用,forward<T>(arg).get() 已经是左值,外部的 forward 什么都不做。

如果 decltype(forward<T>(arg).get()) 是右值引用,forward<T>(arg).get() 已经是 xvalue,外部的 forward 什么都不做。

如果 decltype(forward<T>(arg).get()) 不是引用,forward<T>(arg).get() 是 prvalue,外部的 forward 强制生成一个临时值(结果是一个 xvalue)。*在 C++11/14 中,这会将 prvalue 转换为 xvalue,阻止可选的 RVO。


只有当 expr 是变量名时,才应该 使用 forward<decltype(expr)>(expr)。这是唯一一种情况,其中 decltype(expr) 可以具有不同的引用限定符,与表达式的值类别不同。如果 expr 不是一个名称,最好只写 (expr)

英文:

You are right. The second forward does absolutely nothing, except force a return-by-value function that returns a prvalue to create a temporary (usually unwanted).

If decltype(forward&lt;T&gt;(arg).get()) is an lvalue reference, forward&lt;T&gt;(arg).get() is an lvalue already and the outer forward does nothing.

If decltype(forward&lt;T&gt;(arg).get()) is an rvalue reference, forward&lt;T&gt;(arg).get() is an xvalue already and the outer forward does nothing.

If decltype(forward&lt;T&gt;(arg).get()) is not a reference, forward&lt;T&gt;(arg).get() is a prvalue and the outer forward forces a temporary to be materialized (resulting in an xvalue). *In C++11/14 this would turn a prvalue into an xvalue, inhibiting an optional RVO


forward&lt;decltype(expr)&gt;(expr) should only be used if expr is the name of a variable. Thats the only time that decltype(expr) can have different ref-qualifiers than the value category of the expression. If expr is not a name, it is better to just write (expr).

huangapple
  • 本文由 发表于 2023年7月20日 20:08:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76729690.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定