C++11:std::forward的微妙之处:真的需要identity吗?

13

我设置了一个测试用例来学习完美转发。

std::string inner(const std::string& str ) {
return "const std::string&";
}
std::string inner(std::string& str ) {
    return "std::string&";
}
std::string inner(const std::string&& str ) {
    return "const std::string&&";
}
std::string inner(std::string&& str ) {
    return "std::string&&";
}

template <typename T> void outer(T&& t) {
  std::cout << "t: " << t << std::endl;
  std::cout << "perfect forward: " << inner(std::forward<T>(t)) << std::endl;
  std::cout << std::endl;
}

void PerfectForwarding()
{
     outer("literal");
     outer(lvalue);
     outer(constlvalue);
     outer(rvalue());
     outer(constrvalue());
}

std::forward 的功能符合预期。当我实现自己的 forward 函数时,没有使用 identity 时会出现有趣的行为:

template <typename T> T&& MyForward(T& t)
{
   return ((T&&)t);
}

在外部使用MyForward代替std::forward会得到完全相同的结果!这种行为引发了一个问题,为什么要使用identity?编译器为VS2010。关于防止类型推断,据我所知,特殊的类型推断规则只在T&&上被激活。注意forward的定义,forward(typename identity<T>::type& t)。参数类型只有一个&。事实上,当我改用identity,并省略(T&&)强制转换时,示例无法编译。表面上,从左值到右值的强制转换似乎使forward起作用。在ideone.com上测试GCC 4.5,结果相同。

1
“identity”是什么意思?是指std::forward的两个重载还是remove_reference<T>::type - kennytm
1
identity指的是identity结构体,forward的参数类型为:_Ty&& forward(typename identity<_Ty>::type& _Arg)。 - Candy Chiu
2
那一定是某种预先标准的签名。标准签名是 T&& forward(typename remove_reference<T>::type& t)(还有一个接受 && 的重载)。 - kennytm
1
我使用VS2010编译器。这可能是编译器特定的行为。cppreference.com列出了两个forward的重载。 - Candy Chiu
2
@Candy:VC++ 2010是基于N3000的,它带有std::identity<>;最终的C++11标准是基于N3290的,已经将其删除。 - ildjarn
显示剩余4条评论
2个回答

14

remove_reference<T>identity在旧版本的草案中,但已更改为remove_reference)用于防止类型推断:std::forward 与显式类型参数一起使用。否则以下内容将编译:

std::forward(t)

...但这样做不会得到正确的结果。

关于lvalue/rvalue问题,请注意有两个重载std::forward:一个用于lvalue,另一个用于rvalue。

实际上,给出的MyForward实现更像是std::move:它将lvalue转换为rvalue(不同之处在于move也接受rvalue)。


只有一个VS2010,而且它可以使用。那么我们为什么需要两个呢? - Candy Chiu
@CandyChiu 它在VS2010上运行是因为它在标准确定之前就已经发布了。在VS2010发布和标准发布之间,规则已经发生了变化。我们需要两个,因为lvalue无法绑定到&&,而rvalue无法绑定到& - R. Martinho Fernandes
我明白了。不幸的是,我手头没有另一个编译器来测试这个。 - Candy Chiu
@Candy 如果有帮助的话,http://ideone.com 运行的是GCC 4.5。我不知道他们是如何在那里实现的,但你可以尝试使用自己的实现。我相信它具有正确的rvalue绑定语义。 - R. Martinho Fernandes
我在ideone.com上尝试了一下,保存行为:MyForward和std::forward给出了相同的结果。然而,GCC 4.5将类型T&分配给文字值,而VC2010将类型const T&分配给文字值。 - Candy Chiu
1
好吧,看来4.5还不行:( 顺便说一下,我同时运行GCC 4.7和clang 3.1,它按照我描述的方式工作。 - R. Martinho Fernandes

0

我在VS 2010中查看了forwardidentity的定义。你的MyForward和他们的forward之间唯一的区别在于,你使用了一个T&参数,而他们使用了一个typename identity<T>::type&参数。而identity<T>::type就是T

这个区别最重要的(也许也是唯一的)影响是,使用他们的forward必须明确指定模板参数,而你的MyForward的模板参数可以从调用中推导出来。


是的,如果将一个左值传递给 outer(T&&),t 将被转换为右值引用类型。 - Cosyn

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