为什么std::forward有两个签名?

5

cplusplus.com所述,std::forward有两个签名:

template <class T> T&& forward (typename remove_reference<T>::type& arg) noexcept;
template <class T> T&& forward (typename remove_reference<T>::type&& arg) noexcept;

std::forward 的典型用法是在将参数传递给其他函数时保留 rvalueness。让我们通过一个例子来说明:

void overloaded(int &)  { std::cout << "lvalue"; }
void overloaded(int &&) { std::cout << "rvalue"; }

template <typename T>
void fwd(T && t)
{
  overloaded(std::forward<T>(t));
}

当我们调用fwd(0)时,T 推导为 intt 的类型为 int &&)。然后我们调用 std::forward<int>(t)。该调用的结果是类型为 int && 的表达式,因此选择了第二个版本的 overloaded 函数,并将 "rvalue" 打印到标准输出中。
当我们调用 fwd(i)(其中 i 是某些 int 变量)时,T 推导为 int&t 的类型为 int &)。然后我们调用 std::forward<int&>(t)。该调用的结果(应用引用折叠规则后)是类型为 int & 的表达式,因此选择了第一个版本的 overloaded 函数,并将 "lvalue" 打印到标准输出中。
在这两种情况下,我们使用 std::forward 的第一个重载(采用 typename remove_reference<T>::type& arg)。这是因为即使 t 的类型为 int &&,它也会绑定到左值引用(因为类型为“rvalue reference to something”的命名变量本身是左值,而左值无法绑定到右值引用)。 问题 1: std::forward 的第二个重载是用于什么的?您能想到一些实际的例子,使用采用 rvalue 引用的 arg 的重载吗? 问题 2: cplusplus.com 上说:

Both signatures return the same as:

static_cast<decltype(arg)&&>(arg)
我对此有所疑虑,因为我相信它是错误的。当我们尝试从第一个重载的std::forward返回时,会出现编译错误。
当使用int右值调用fwd时,它将使用T = int调用std::forward的第一个重载。然后,decltype(arg)将变为int&,因此static_cast<decltype(arg)&&>(arg)将折叠为static_cast<int&>(arg)。但是返回类型为int &&,因此我们会遇到编译错误:
cannot bind ‘std::remove_reference<int>::type {aka int}’ lvalue to ‘int&&’

两个重载版本的std::forward都应该返回static_cast<T&&>(arg)。我说得对吗?

你认为cplusplus.com上引用的这段话是错误的吗?


请参考 https://dev59.com/WFoT5IYBdhLWcg3wpQuD 获取第一个问题的答案。 - TartanLlama
@TartanLlama 那个有很多constconst_cast散布在其中。 - Yakk - Adam Nevraumont
4
cplusplus.com 不是 cppreferencecppreference.comcppreference。要区分清楚它们之间的差异。 - Nawaz
@Nawaz 指出差异可能与主题无关(我个人认为 cppreference 更可靠)。 - Yakk - Adam Nevraumont
@Yakk:我甚至不看cplusplus.com,因为我的经验:cplusplus.com有什么问题? - Nawaz
3
@Yakk 如果你发现了一个 bug,你始终可以自行修复 :) - T.C.
1个回答

1
class Foo {};

auto f = [](){ return Foo{}; };
auto g = []()->Foo&{ static Foo x; return x; };

template<class T>
std::false_type is_rvalue( T& ) { return {}; }
template<class T>
std::true_type is_rvalue( T&& ) { return {}; }

template<class T, class F>
auto test( F&& f ) {
  return is_rvalue( std::forward<T>( f() ) );
}

int main() {
  std::cout << test<Foo>(f) << "," << test<Foo&>(g) << "\n";
}

这有点牵强,但是想象一下,你有一个工厂可能会根据一些不重要的细节生产FooFoo&,但你知道你想要基于第二个类型T将其转发到T&&。你知道如果T不是引用,那么它就不会产生Foo,但如果T是引用,它可能会产生。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接