执行std :: forward

时间:2014-12-16 09:32:48

标签: c++ c++11

我正在阅读Overview of the New C++ (C++11/14) (PDF only),在幻灯片288中,它提供了std::forward的实现:

template<typename T>                // For lvalues (T is T&),
T&& std::forward(T&& param)         // take/return lvalue refs.
{                                   // For rvalues (T is T),
    return static_cast<T&&>(param); // take/return rvalue refs.
}

然后在文本中给出另一个实现:

  

通常的std :: forward实现是:

template<typename T>
struct identity {
    typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{ return static_cast<identity<T>::type&&>(param); }

有什么区别?为什么后者通常是实施?

4 个答案:

答案 0 :(得分:20)

第一个问题是你可以写std::forward(x),它不能做你想要的,因为它总是产生左值引用。

第二种情况中的参数是一个非推导的上下文,阻止了模板参数的自动推导。这迫使你写std::forward<T>(x),这是正确的事情。

此外,第二次重载的参数类型应为typename identity<T>::type&,因为std::forward的惯用语输入始终是左值。

编辑:标准实际上要求签名等同于此签名(顺便提一下,这正是libc ++所具有的):

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

答案 1 :(得分:11)

libc ++中的实现使用std::remove_reference和两个重载。这是源(删除一些宏之后):

template <class T>
inline T&& forward(typename std::remove_reference<T>::type& t) noexcept
{
    return static_cast<T&&>(t);
}

template <class T>
inline T&& forward(typename std::remove_reference<T>::type&& t) noexcept
{
    static_assert(!std::is_lvalue_reference<T>::value,
                  "Can not forward an rvalue as an lvalue.");
    return static_cast<T&&>(t);
}

但请注意,在C ++ 14中,std::forwardconstexpr

答案 2 :(得分:3)

Sebastian Redl所说的第一个案例总会给你一个左值参考。原因是参数中的右值引用将作为左值引用传递,参数android.intent.action.BOOT_COMPLETED类型为universal reference而不是右值引用。

实际上,如果第一种情况是正确的,我们甚至不再需要T&&。这是一个演示如何传递通用引用参数的实验

forward

该程序证明template <typename T, typename U> void g(T&& t, U&& u) { std::cout << "t is lvalue ref: " << std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1 std::cout << "t is rvalue ref: " << std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0 std::cout << "u is lvalue ref: " << std::is_lvalue_reference<decltype(u)>::value << std::endl; // 1 std::cout << "u is rvalue ref: " << std::is_rvalue_reference<decltype(u)>::value << std::endl; // 0 } template <typename T, typename U> void f(T&& t, U&& u) { std::cout << "t is lvalue ref: " << std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1 std::cout << "t is rvalue ref: " << std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0 std::cout << "u is lvalue ref: " << std::is_lvalue_reference<decltype(u)>::value << std::endl; // 0 std::cout << "u is rvalue ref: " << std::is_rvalue_reference<decltype(u)>::value << std::endl; // 1 g(t, u); } int main() { std::unique_ptr<int> t; f(t, std::unique_ptr<int>()); return 0; } tu传递到f都是左值引用,尽管gu中的右值引用{1}}。因此,在第一种情况下,f的参数只是没有机会成为右值参考。

forward用于将参数类型从通用引用更改为右值引用(如Redl所述,使用identity更精确)。但是,此更改不再允许模板类型推导,因此std::remove_reference的类型参数是必需的,因此我们将编写forward

但你问题中的第二个案例也不正确,正如Redl所提到的,正确的方法是重载,其参数是左值参考。

我能找到的最直接的实现是

forward<T>(t)

它适用于通用引用,例如

template <typename T>
T&& forward(typename identity<T>::type& param)
{
    return static_cast<T&&>(param);
}

答案 3 :(得分:0)

回复

<块引用>

有什么区别?

在实践中没有区别,执行的预期结果是一样的,只是写的方式更冗长

您只是定义 T 并使用通过 typename 定义的类型,这意味着实际上没有区别。

<块引用>

为什么后者是通常的实现方式?

我没有关注这个更新,所以我不能说他们是否在他们的存储库中正式使用了这段代码,这可能是别人的实现而不是官方的,不管是什么原因:

他们选择了这种方式,尽管两种实现对于实际场景都是无效的

注意两者的结果是一样的,在执行过程中根本没有分歧,也就是说,在实际场景中,它们之间的分歧不会导致任何差异或运行/编译错误或类似的事情

代码审查

错过添加“typename”以使编译成功

template<typename T>
struct identity {
    typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{
    return static_cast<**typename** identity<T>::type&&>(param);
}

说明

为什么无效

考虑下面的代码,参数 T 是“int”,包含返回类型是“int&&”,一起期待右值 (int&&) 类型的函数参数,但传递了左值 int& 生成编译错误
#include <iostream>

template<typename T>         
T&& forward(T&& param)    
{                                   
    return static_cast<T&&>(param);
}

int main() {
  int value = 5;
  forward<int>(value);
  return 1;
}

真实的风景

第 25 行 redir(5) 导致编译错误,这将是一个真实的场景。

该错误是因为引用 void redir模板 T 属于 int 类型,并且在调用 forward< int > 时(param) 它正在传递 param 这是一个左值变量,在 Why is invalid

部分中进行了解释
#include <iostream>

template<typename T>         
T&& forward(T&& param)    
{                                   
    return static_cast<T&&>(param);
}

void print(int &&value){
  std::cout << "rvalue: " << value << std::endl; 
}

void print(int &value){
  std::cout << "lvalue: " << value << std::endl; 
}

template <class T>
void redir(T &&param){
  print(forward<T>(param));
}

int main() {
  int value = 5;
  redir(value);
  **redir(5);**
  return 0;
}

解决方案

目前代码已更新,问题已修复,您可以查看:https://en.cppreference.com/w/cpp/utility/forward。

代码类似于:

#include <iostream>

template< class T >
T&& forward( std::remove_reference_t<T> &&param)
{                                   
  return static_cast<T&&>(param);
}

template< class T >
T&& forward( std::remove_reference_t<T> &param)
{                                   
  return static_cast<T&&>(param);
}

void print(int &&value){
  std::cout << "rvalue: " << value << std::endl; 
}

void print(int &value){
  std::cout << "lvalue: " << value << std::endl; 
}

template <class T>
void redir(T &&param){
  print(forward<T>(param));
}

int main() {
  int value = 5;
  redir(value);
  redir(5);
  return 0;
}

std::remove_reference_t 是可选的,即使不使用它,结果也是一样的。

由于良好的实践,他们决定使用 remove_reference_t,也就是说,他们强调第一个函数需要 int&,第二个函数需要 int&&。

为什么 std::remove_reference 无关紧要

虽然它不会改变代码执行中的预期结果,但由于良好的编程实践,它很重要,可以说它加强了预期结果。

好吧,但是结果解释没有改变是由于以下转换规则:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

正如我们所知,获得右值 (T&&) 的唯一方法是通过转换 T&& + T&& 或仅 T&&。

为什么需要在一个函数中使用 2 个函数 (std::forward) 而不仅仅是 static_cast

<int&&> int&& && forward(int&& &&param) will result int&& forward(int&& param)
<int&> int& && forward(int& &&param) will result int& forward(int& param)
<int> int && forward(int &&param) will result int&& forward(int&& param)

注意第二个函数:T&& forward(std::remove_reference_t ¶m) 填补了缺失的部分

<int> int && forward(int &param) will result int&& forward(int& param)

因此,您需要声明 2 个 std::forward 函数。