move和forward为什么使用方式不同(聊聊forward为什么不让右值以左值的形式转出)

前言

我之前提出过问题move和forward为什么使用方式不同,以及哪里不同。一下就会解释。

一、move和forward哪里不同,为什那么不同?

1.哪里不同

  1. 使用方式,发现了吧move不需要制定类型,forward使用的时候需要制定类型才能使用。
  2. 源码里实现方式不同,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);
}
  1. 为什么move不需要指定类型,而forward使用需要指定类型。
    move可以根据(_Ty&& _Arg)模板类型推导自己推导出_Ty的类型,因此不需要指定。显然forward做不到,remove_reference_t<_Ty>&& _Arg,他用的是擦除类型之后的,无法利用模板推导自行获取类型,因此必须外界指定。
  2. 为什么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.为什么设计两个函数

那为什么还要设计两个,提取参数类型干什么呢。

  1. 这就是核心问题,因为定义就是这样的。
  2. 根据源码看,forward就是不能让右值以左值的性质传出。
  3. 但是不妨碍我们推导一下,就两种特殊情况,左值传入可以右值传出,右值传入却不能以左值传出。区别在哪里呢。你是不是突然想明白了,你不能对一个右值强制转化为左值。但是在模板里你可以,为什么,因为你右值的参数传入就会变成左值了,因此为了维护语言,就必须加上此断言,就是不能让右值以左值的形式转出,这违背语言的特点。
  4. 使用static_assert,我们可以在编译期间发现更多的错误,用编译器来强制保证一些契约,并帮助我们改善编译信息的可读性,尤其是用于模板的时候。

总结

我认为之所是这样是实现为了维护语言特性。如果有正确的解释希望评论说一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值