【问题标题】:The implementation of std::forwardstd::forward 的实现
【发布时间】:2014-12-16 09:32:48
【问题描述】:

我正在阅读 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);
}

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

【问题讨论】:

  • 在第一种情况下,您可以使用std::forward(x),但在第二种情况下,您必须显式提供模板参数,因为它们无法推断。
  • @PiotrS。不,你没有。为什么有必要这样做?引用折叠在直接转发引用上下文之外工作。
  • @SebastianRedl 错过了它的身份,而不是 remove_reference
  • @PiotrS。你是对的,左值需要被接受。但是,右值不需要,因此解决方案不是第二次重载,解决方案是始终在存在的那个中获取左值引用。
  • @PiotrS。结果我错了,标准要求接受右值。

标签: c++ c++11


【解决方案1】:

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

第二种情况的参数是非推导上下文,防止模板参数的自动推导。这迫使你写std::forward&lt;T&gt;(x),这是正确的做法。

另外,第二个重载的参数类型应该是typename identity&lt;T&gt;::type&amp;,因为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;

【讨论】:

  • 我明白为什么在参数中使用身份,但为什么不只是return static_cast&lt;T &amp;&amp;&gt;(param);
  • @kec 当static_cast&lt;T&amp;&amp;&gt;(param) 是移动还是向前取决于 T 是否为通用引用时,如果没有同名的实用函数,则教授移动语义和完美转发要困难得多。实用功能有助于提高可读性并防止意外移动。
  • standard/libc++ 中使用的签名优于两个教科书示例,因为它允许 std::forward 也用于 C 样式指针。这些不会随教科书示例一起转发。
  • 请参阅en.cppreference.com/w/cpp/language/template_argument_deduction 了解有关非推断上下文的更多详细信息。
【解决方案2】:

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

【讨论】:

  • 是否有任何真实场景会导致 static_assert 失败?
  • @xinwang forward&lt;int&amp;&gt;(5)forward&lt;std::string&amp;&gt;(std::string{})
  • 即使没有static_assert 编译器也会将此标记为错误,例如godbolt.org/z/55Y4va
  • 参数类型应该是typename std::remove_reference&lt;T&gt;::type&amp;&amp; ,而不是T&amp;&amp;
【解决方案3】:

Sebastian Redl 所说的第一个案例总是会给你一个左值引用。原因是参数中的右值引用会作为左值引用传递,而参数T&amp;&amp;类型是universal reference而不是右值引用。

实际上,如果第一种情况是正确的,我们甚至不再需要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;
}

程序证明tuf 传递到g 都是左值引用,尽管uf 中的右值引用。所以在第一种情况下,forward 的参数没有机会成为右值引用。

identity 用于将参数类型从通用引用更改为右值引用(如 Redl 所述,使用 std::remove_reference 更精确)。但是这个改动使得模板类型推导不再可能,所以forward的类型参数是强制的,所以我们写forward&lt;T&gt;(t)

但您问题中的第二种情况也不正确,正如 Redl 也提到的,正确的方法是重载,其参数是左值引用。

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

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

它适用于通用引用,例如

template <typename T, typename U>
void f(T&& t, U&& u)
{
    ::forward<T>(t);
    ::forward<U>(u);
}

std::unique_ptr<int> t;
f(t, std::unique_ptr<int>());
// deduction in f:
//   T = unique_ptr&, decltype(t) = unique_ptr&
//   U = unique_ptr, decltype(u) = unique_ptr&& (but treated as an lvalue reference)
// specialization of forward:
//   forward<T> = forward<unique_ptr&>, param type = unique_ptr&
//                                      return type = unique_ptr&
//   forward<U> = forward<unique_ptr>,  param type = unique_ptr&
//                                      return type = unique_ptr&&

【讨论】:

    【解决方案4】:

    回复

    有什么区别?

    实际上没有区别,执行的预期结果是一样的,只是写法更冗长

    您只是定义 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 redirtemplate Tint 类型并且在调用 forward 时(param) 它正在传递 param 这是一个左值变量,在为什么无效

    部分中进行了解释
    #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 &param) 仅填充缺少的内容

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-07-08
      • 1970-01-01
      • 2015-05-03
      • 2014-10-15
      • 2014-06-12
      • 1970-01-01
      • 2021-08-08
      相关资源
      最近更新 更多