The implementation of std::forward

The implementation of std::forward

本文关键字:forward std of implementation The      更新时间:2023-10-16

>我正在阅读新C++概述 (C++11/14((仅限 PDF(,在幻灯片 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),它不会做你想要的,因为它总是产生左值引用。

第二种情况下的参数是非推导上下文,防止自动推导模板参数。这迫使你写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;

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 .

塞巴斯蒂安·雷德尔(Sebastian Redl(所说的第一种情况将始终为您提供一个正确的参考。原因是参数中的右值引用将作为左值引用传递,并且参数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;
}

事实证明,从f传递到gtu都是左值引用,尽管uf中的右值引用。因此,在第一种情况下,forward 的参数没有机会成为右值引用。

identity用于将参数类型从通用引用更改为右值引用(如 Redl 所述,使用 std::remove_reference 更精确(。但是,此更改使模板类型推断不再可能,因此forward的类型参数是必需的,因此我们将编写forward<T>(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&&

回复

有什么区别?

在实践中没有区别,执行的预期结果是相同的,因为只是写作方式更冗长

你只是在类型定义T并使用通过类型名定义的类型,这意味着字面上没有区别。

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

我没有遵循此更新,所以我不能说他们是否在他们的存储库中正式使用此代码,这可能是其他人的实现而不是官方的,无论原因是什么:

他们选择这样做,即使这两种实现对于实际场景都是无效的。

请注意,两者具有相同的结果,并且在执行中根本不会发散,也就是说,在实际场景中,它们之间的发散不会导致任何差异或运行/编译错误或类似的事情

代码审查

错过添加"类型名称"以使编译成功

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&&",一起期望一个类型为 rvalue (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,并且在向前调用时(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 函数。

根据右值参考建议,命名右值与左值没有什么不同,除了decltype。也就是说,函数的任何命名参数都不能隐式强制转换或用于初始化另一个右值引用;它仅复制到左值引用;但static_cast可以显式转换引用的值。因此,当您将命名(与未命名的临时相反(对象/变量传递给函数时,它只能由 lvlaues 捕获。将函数参数转换为 rvalue 是必要的static_cast,但它对其输出类型的值性过于冗长。任何函数(如 std::forward(都无法推断其命名操作数的decltype。因此,类型必须作为模板类型参数详细传递;正确推导返回类型的forward依赖于其类型参数。这个特殊函数的两个重载是必要的:左值重载捕获命名的右值,右值重载捕获无法直接绑定到左值引用的未命名左值。通过使用模板类型alise来防止输入引用的基础类型的推断,并通过将它们嵌套在特征类型中来强调;要么std::type_identity要么std::remove_reference否则。这使程序员远离在使用std::forward时无意中传递错误值的陷阱。我还没有看到一个技巧可以实现std::forward的功能,同时省略其详细类型参数。这是一件精美的艺术品。

问候调频。