从 C++11 开始理解 std::forward 的实现
Understanding of the implementation of std::forward since C++11
(constexpr
和 noexcept
被排除在外,因为它们似乎与理解 std::forward
的行为无关。)
根据我对 Scott Meyers 的理解 "Effective Modern C++",
std::move
在 C++14 中的示例实现如下
template<typename T>
decltype(auto) move(T&& param) {
return static_cast<remove_reference_t<T>&&>(param);
}
考虑到什么是转发(或 "universal")引用,我认为这个实现对我来说非常清楚:
- 参数
param
的类型为T&&
,即右值引用或左值引用(无论参数的类型是什么),取决于参数是右值还是左值呼叫者,召集者;换句话说 param
可以绑定到右值和左值(即 anything);这是有意为之的,因为 move
应该将 anything 转换为右值。
decltype(auto)
只是根据实际return
语句表达return类型的简洁方式。
- returned 对象是同一个对象
param
,转换为右值引用 (&&
) 到任何 T
类型,一旦其推导出引用性被剥离(扣除是在 T&&
上完成的,而不是在 ⋯<T>&&
上完成的)。
简而言之,我对move
实现中使用forwarding/universal引用的理解如下:
- forwarding/universal 引用
T&&
用于参数,因为它旨在绑定到任何东西;
- return 类型是右值引用,因为
move
旨在将任何内容转换为右值。
很高兴知道到目前为止我的理解是否正确。
另一方面,std::forward
在 C++14 中的示例实现如下
template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
我的理解是这样的:
T&&
,return 类型, 必须是 forwarding/universal 引用,因为我们希望 forward
到 return右值引用或左值引用,因此类型推导发生在 return 类型上(不同于 move
发生的情况,其中类型推导发生在参数端) 是一个右值对传递给 forward
; 的模板类型参数的引用
- 因为
T
编码了实际参数的 lvalue/rvalue-ness,它绑定了作为参数传递给 forward
的调用者参数,T
本身可能导致actual_type&
或 actual_type
,因此 T&&
可以是左值引用或右值引用。
param
的类型是对任何类型 T
的左值引用,一旦其 推导的 引用被剥离。
我的疑惑如下
-
forward
的两个实例(实际上每个调用它的类型都有两个)仅在 return 类型上有所不同(传递右值时的右值引用,传递右值时的左值引用传递了左值),因为在这两种情况下 param
都是对非 const
无引用 T
的左值引用类型。 return 类型不是不计入重载决议的东西吗? (也许我在这里使用 "overload" 不当。)
- 由于
param
的类型是对无引用 T
的非 const
左值引用,并且左值引用必须是对 const
的顺序绑定到右值,param
如何绑定到右值?
作为附带问题:
- 可以将
decltype(auto)
用于 return 类型,就像 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 左值引用类型。 T&&
与 T
相同,而 forward<T>(arg)
是一个 const 左值表达式。因此,f
是用 const 左值表达式调用的。
如果使用右值调用 g
,则 T
被推断为非引用类型。 T&&
是右值引用类型,forward<T>(arg)
是右值表达式。因此,f
是用右值表达式调用的。
在所有情况下,都尊重值类别。
普通完美转发没有使用二次重载。请参阅 了解其用法。
(constexpr
和 noexcept
被排除在外,因为它们似乎与理解 std::forward
的行为无关。)
根据我对 Scott Meyers 的理解 "Effective Modern C++",
std::move
在 C++14 中的示例实现如下
template<typename T>
decltype(auto) move(T&& param) {
return static_cast<remove_reference_t<T>&&>(param);
}
考虑到什么是转发(或 "universal")引用,我认为这个实现对我来说非常清楚:
- 参数
param
的类型为T&&
,即右值引用或左值引用(无论参数的类型是什么),取决于参数是右值还是左值呼叫者,召集者;换句话说param
可以绑定到右值和左值(即 anything);这是有意为之的,因为move
应该将 anything 转换为右值。 decltype(auto)
只是根据实际return
语句表达return类型的简洁方式。- returned 对象是同一个对象
param
,转换为右值引用 (&&
) 到任何T
类型,一旦其推导出引用性被剥离(扣除是在T&&
上完成的,而不是在⋯<T>&&
上完成的)。
简而言之,我对move
实现中使用forwarding/universal引用的理解如下:
- forwarding/universal 引用
T&&
用于参数,因为它旨在绑定到任何东西; - return 类型是右值引用,因为
move
旨在将任何内容转换为右值。
很高兴知道到目前为止我的理解是否正确。
另一方面,std::forward
在 C++14 中的示例实现如下
template<typename T>
T&& forward(remove_reference_t<T>& param) {
return static_cast<T&&>(param);
}
我的理解是这样的:
T&&
,return 类型,必须是 forwarding/universal 引用,因为我们希望是一个右值对传递给forward
到 return右值引用或左值引用,因此类型推导发生在 return 类型上(不同于move
发生的情况,其中类型推导发生在参数端)forward
; 的模板类型参数的引用
- 因为
T
编码了实际参数的 lvalue/rvalue-ness,它绑定了作为参数传递给forward
的调用者参数,T
本身可能导致actual_type&
或actual_type
,因此T&&
可以是左值引用或右值引用。 param
的类型是对任何类型T
的左值引用,一旦其推导的引用被剥离。
我的疑惑如下
-
forward
的两个实例(实际上每个调用它的类型都有两个)仅在 return 类型上有所不同(传递右值时的右值引用,传递右值时的左值引用传递了左值),因为在这两种情况下param
都是对非const
无引用T
的左值引用类型。 return 类型不是不计入重载决议的东西吗? (也许我在这里使用 "overload" 不当。) - 由于
param
的类型是对无引用T
的非const
左值引用,并且左值引用必须是对const
的顺序绑定到右值,param
如何绑定到右值?
作为附带问题:
- 可以将
decltype(auto)
用于 return 类型,就像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 左值引用类型。T&&
与T
相同,而forward<T>(arg)
是一个 const 左值表达式。因此,f
是用 const 左值表达式调用的。如果使用右值调用
g
,则T
被推断为非引用类型。T&&
是右值引用类型,forward<T>(arg)
是右值表达式。因此,f
是用右值表达式调用的。
在所有情况下,都尊重值类别。
普通完美转发没有使用二次重载。请参阅