英文:
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<class T>
void wrapper(T&& arg)
{
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}
> where the type of arg may be
struct Arg
{
int i = 1;
int get() && { return i; } // call to this overload is rvalue
int& get() & { 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&& get() && { return move(inner); } // call to this overload is rvalue
Inner& get() & { return inner; } // call to this overload is lvalue
const Inner& get() const & { return inner; }; // call to this overload is also 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)
{
// NOTE: choose either the former or the latter version!
// with extra forward
foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
// without extra forward
foo(forward<T>(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:
&&
&&
&
const&
答案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<T>(arg).get())
is an lvalue reference, forward<T>(arg).get()
is an lvalue already and the outer forward does nothing.
If decltype(forward<T>(arg).get())
is an rvalue reference, forward<T>(arg).get()
is an xvalue already and the outer forward does nothing.
If decltype(forward<T>(arg).get())
is not a reference, forward<T>(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<decltype(expr)>(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)
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论