自 C++11 年以来对 std::forward 实施的理解

Understanding of the implementation of std::forward since C++11

本文关键字:forward std C++11      更新时间:2023-10-16

(constexprnoexcept被排除在外,因为它们似乎与理解std::forward的行为无关。

根据我对斯科特·迈耶斯(Scott Meyers)的"有效的现代C++"的理解, C++14 中std::move的示例实现如下

template<typename T>
decltype(auto) move(T&& param) {
return static_cast<remove_reference_t<T>&&>(param);
}

鉴于转发(或"通用")引用是什么的解释,我认为这个实现对我来说很清楚:

参数param的类型为T&&,即 右值引用或左值
  • 引用(无论参数的类型是什么),这取决于参数在调用者中是右值还是左值;换句话说,param可以绑定到右值和左值(即任何东西);这是有意的,因为move应该将任何东西转换为右值。
  • decltype(auto)只是根据实际return语句表示返回类型的简洁方法。
  • 返回的对象是相同的对象param,一旦它的推导引用被剥离(推导的引用性),就强制转换为右值引用(&&)到任何类型T(推导是在T&&上完成的,而不是在⋯<T>&&上完成的)。

简而言之,我对在实现move中使用转发/通用引用的理解如下:

  • 转发/通用引用T&&用于参数,因为它旨在绑定到任何内容;
  • 返回类型是右值引用,因为move旨在将任何内容转换为右值。

很高兴知道到目前为止我的理解是否正确。

另一方面,C++14 中std::forward的示例实现如下

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

我的理解如下:

  • T&&,返回类型,必须是转发/通用引用,因为我们希望forward通过右值引用或左值引用返回,因此这里的返回类型进行类型推断(不像move发生的情况,其中类型推断发生在参数端)是对传递给forward的任何模板类型参数的右值引用;
  • 由于T编码实际参数的左值/右值,该参数绑定了作为参数传递给forward的调用方参数,T本身可以导致actual_type&actual_type,因此T&&可以是左值引用或右值引用。
  • param的类型是T类型的左值引用,一旦其推导的引用性被剥离。 实际上,在std::forward类型推断是故意禁用的,需要显式传递模板类型参数。

我的疑问如下。

  • forward的两个实例(实际上,每个调用它的类型两个)仅返回类型不同(传递右值时的右值引用,传递左值时的左值引用),因为在这两种情况下,param的类型都是对非const无引用T的左值引用。返回类型不计入重载解析吗?(也许我在这里不恰当地使用了"重载"。
  • 由于param的类型是非const无引用T的左值引用,并且由于左值引用必须const才能绑定到右值,param如何绑定到右值?

作为一个附带问题:

  • decltype(auto)可以用于返回类型,就像它用于move一样吗?

forward本质上是一种在完美转发中保存价值类别的机器。

考虑一个简单的函数,它尝试透明地调用f函数,同时尊重值类别。

template <class T>
decltype(auto) g(T&& arg)
{
return f(arg);
}

在这里,问题是表达式arg始终是左值,无论arg是否为右值引用类型。 这就是forward派上用场的地方:

template <class T>
decltype(auto) g(T&& arg)
{
return f(forward<T>(arg));
}

考虑std::forward的参考实现:

template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
constexpr T&& forward(remove_reference_t<T>&& t) noexcept
{
static_assert(!std::is_lvalue_reference_v<T>);
return static_cast<T&&>(t);
}

(您可以在此处使用decltype(auto),因为推导的类型将始终T&&

在以下所有情况下,调用第一个重载是因为表达式arg表示变量,因此是左值:

  • 如果使用非常量左值调用g,则T被推导为非常量左值引用类型。T&&T相同,forward<T>(arg)是一个非常量的左值表达式。 因此,f是使用非常量左值表达式调用的。

  • 如果使用 const 左值调用g,则T被推导为 const lvalue 引用类型。T&&T相同,forward<T>(arg)是一个常量左值表达式。 因此,f是使用 const 左值表达式调用的。

  • 如果使用右值调用g,则T推导为非引用类型。T&&是右值引用类型,forward<T>(arg)是右值表达式。 因此,f是使用 rvalue 表达式调用的。

在所有情况下,都尊重价值类别。

第二个重载不用于正常完美转发。 请参阅 std::forward() 的右值引用重载的目的是什么?为其使用。