文章目录
前言
我之前提出过问题move和forward为什么使用方式不同,以及哪里不同。一下就会解释。
一、move和forward哪里不同,为什那么不同?
1.哪里不同
- 使用方式,发现了吧move不需要制定类型,forward使用的时候需要制定类型才能使用。
- 源码里实现方式不同,move只需要一个强转就可以,而forward却需要两个。这个不知道你已疑问过没有。要是有那正好我们聊聊,我说一些我的理解。
std::forward<int>(10);
std::move(100);
2.为什么不同
因为源码设计方式不同。
源码代码如下:
//forward源码
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(
remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
return static_cast<_Ty&&>(_Arg);
}
template <class _Ty>
_NODISCARD constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept { // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
return static_cast<_Ty&&>(_Arg);
}
//move源码
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
- 为什么move不需要指定类型,而forward使用需要指定类型。
move可以根据(_Ty&& _Arg)模板类型推导自己推导出_Ty的类型,因此不需要指定。显然forward做不到,remove_reference_t<_Ty>&& _Arg,他用的是擦除类型之后的,无法利用模板推导自行获取类型,因此必须外界指定。 - 为什么forward需要设计两个函数,我认为一个函数就够。
那我们就得后续聊聊,看我理解的对不对。
二、为什么forward需要设计两个函数,我认为一个函数就够
1.假如forward只有一个函数。
测试代码如下
#include<iostream>
template <class _Ty, _Ty _Val>
struct integral_constant {
static constexpr _Ty value = _Val;
using value_type = _Ty;
using type = integral_constant;
constexpr operator value_type() const noexcept {
return value;
}
_NODISCARD constexpr value_type operator()() const noexcept {
return value;
}
};
template <bool _Val>
using bool_constant = integral_constant<bool, _Val>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
template <class>
_INLINE_VAR constexpr bool is_lvalue_reference_v = false; // determine whether type argument is an lvalue reference
template <class _Ty>
_INLINE_VAR constexpr bool is_lvalue_reference_v<_Ty&> = true;
template <class _Ty>
struct is_lvalue_reference : bool_constant<is_lvalue_reference_v<_Ty>> {};
template <class>
_INLINE_VAR constexpr bool is_rvalue_reference_v = false; // determine whether type argument is an rvalue reference
template <class _Ty>
_INLINE_VAR constexpr bool is_rvalue_reference_v<_Ty&&> = true;
template <class _Ty>
struct is_rvalue_reference : bool_constant<is_rvalue_reference_v<_Ty>> {};
template <class>
_INLINE_VAR constexpr bool is_const_v = false; // determine whether type argument is const qualified
template <class _Ty>
_INLINE_VAR constexpr bool is_const_v<const _Ty> = true;
template <class _Ty>
struct is_const : bool_constant<is_const_v<_Ty>> {};
template <class _Ty>
struct remove_reference {
using type = _Ty;
using _Const_thru_ref_type = const _Ty;
};
template <class _Ty>
struct remove_reference<_Ty&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&;
};
template <class _Ty>
struct remove_reference<_Ty&&> {
using type = _Ty;
using _Const_thru_ref_type = const _Ty&&;
};
template <class _Ty>
using remove_reference_t = typename remove_reference<_Ty>::type;
forward源码
//template <class _Ty>
//constexpr _Ty&& forward(
// remove_reference_t<_Ty>& _Arg) noexcept { // forward an lvalue as either an lvalue or an rvalue
// bool flag1 = is_lvalue_reference<_Ty>::value;
// bool flag2 = is_rvalue_reference<_Ty>::value;
// bool flag3 = is_const<_Ty>::value;
// std::cout << "左值:" << flag1
// << " :右值 " << flag2
// << " :常量 " << flag3 << std::endl;
// bool flag1s = is_lvalue_reference<_Ty&&>::value;
// bool flag2s = is_rvalue_reference<_Ty&&>::value;
// bool flag3s = is_const<_Ty&&>::value;
// std::cout << "左值:" << flag1s
// << " :右值 " << flag2s
// << " :常量 " << flag3s << std::endl;
// return static_cast<_Ty&&>(_Arg);
//}
template <class _Ty>
constexpr _Ty&& forward(remove_reference_t<_Ty>/*&&*/ _Arg) noexcept { // forward an rvalue as an rvalue
// static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
bool flag1 = is_lvalue_reference<_Ty>::value;
bool flag2 = is_rvalue_reference<_Ty>::value;
bool flag3 = is_const<_Ty>::value;
std::cout << "左值:" << flag1
<< " :右值 " << flag2
<< " :常量 " << flag3 << std::endl;
bool flag1s = is_lvalue_reference<_Ty&&>::value;
bool flag2s = is_rvalue_reference<_Ty&&>::value;
bool flag3s = is_const<_Ty&&>::value;
std::cout << "左值:" << flag1s
<< " :右值 " << flag2s
<< " :常量 " << flag3s << std::endl;
return static_cast<_Ty&&>(_Arg);
}
int main(void)
{
int a = 10;
forward<int>(a);//&&
std::cout << "-------------" << std::endl;
forward<int&>(a);//&
std::cout << "-------------" << std::endl;
forward<int&&>(a);//&&
std::cout << "-------------" << std::endl;
forward<int>(10);//&&
std::cout << "-------------" << std::endl;
forward<int&>(10);//&
std::cout << "-------------" << std::endl;
forward<int&&>(10);//&&
std::cout << "-------------" << std::endl;
return 0;
}
结果如下
你发现了吧一样实现,没有任何问题。
2.为什么设计两个函数
那为什么还要设计两个,提取参数类型干什么呢。
- 这就是核心问题,因为定义就是这样的。
- 根据源码看,forward就是不能让右值以左值的性质传出。
- 但是不妨碍我们推导一下,就两种特殊情况,左值传入可以右值传出,右值传入却不能以左值传出。区别在哪里呢。你是不是突然想明白了,你不能对一个右值强制转化为左值。但是在模板里你可以,为什么,因为你右值的参数传入就会变成左值了,因此为了维护语言,就必须加上此断言,就是不能让右值以左值的形式转出,这违背语言的特点。
- 使用static_assert,我们可以在编译期间发现更多的错误,用编译器来强制保证一些契约,并帮助我们改善编译信息的可读性,尤其是用于模板的时候。
总结
我认为之所是这样是实现为了维护语言特性。如果有正确的解释希望评论说一下。