V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  C++

std::move 的实现使用到了万能引用?

  •  
  •   amiwrong123 · 155 天前 · 2253 次点击
    这是一个创建于 155 天前的主题,其中的信息可能已经有所发展或是发生改变。

    https://www.cnblogs.com/shadow-lr/p/14748272.html

    // FUNCTION TEMPLATE move
    template <class _Ty>
    _NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept {
        return static_cast<remove_reference_t<_Ty>&&>(_Arg);
    }
    
    
    • 我看这个形式是完全符合万能引用的,所以 std::move 理论上来说只需要这一个实现就行了,是吗?因为任何引用类型(左值引用、右值引用)都可以被 万能引用接受
      • (而且我理解,一个非引用类型也可以被万能引用接受吧?就相当于T&去接一个T,这是另一个问题)如果可以的话,那万能引用就是任何类型都能接得。
    • 为什么 std::move 要加 constexpr 呀,那返回值类型不就变成了const T&&,这是一个底层 const ,具有底层 const 的引用在传参过程是不能丢失掉底层 const 的,所以 这不是减少了 std::move 返回值的使用范围了吗?
      • 而且我看其他博客里,std::move 是没有带 constexpr 的,所以到底带不带?
    第 1 条附言  ·  155 天前
    #include <iostream>
    using namespace std;
    
    class A {};
    
    template <class _Ty>
    void fun_test(_Ty&& _Arg) {
        cout << "wanneng" << endl;
    }
    template <class _Ty>
    void fun_test(_Ty& _Arg) {
        cout << "left" << endl;
    }
    
    int main()
    {
        A a;
        fun_test(a);
    }
    

    现在我写了两个函数,第一个是万能引用版本,第二个是左值引用版本。 我理解,因为有了万能引用版本,万能引用什么类型都可以接,所以不可能调用到 第二个函数了.

    但是,打印的是 left 这是为什么?

    13 条回复    2022-01-28 17:56:49 +08:00
    zzxxisme
        1
    zzxxisme  
       155 天前   ❤️ 2
    尝试回答一下:
    1. 利用_Ty&&可以接受所有引用类型。如果是左值引用,例如 int&,那么这里"_Ty"就是"int&",整个"_Ty&&"就是"int& &&"。如果是右值引用,例如 int&&,那么_Ty 就是 int 。如果传的是一个值,例如 int ,这样_Ty 也是 int 。
    2. std::move 加一个 constexpr 并不意味着返回值一定要加 const ,这个是对函数加的 constexpr ,它意味着这个 std::move 可以在用在一个 constant expression 里面,这样,编译器可以在编译期间进行这个 move 。这个 constexpr 是 c++17 之后加进来的,std::move 是 c++11 就加进来的,我猜可能你看过一些版本是 c++17 之前的?
    amiwrong123
        2
    amiwrong123  
    OP
       155 天前
    @zzxxisme #1
    所以,std::move 只有这一个实现也能正常工作,因为它什么类型都可以接,是吧。

    附言里我加的这个问题,为什么有了万能引用版本的函数,还能调用到左值引用版本的函数呀?有点不理解了,老哥

    >std::move 加一个 constexpr 并不意味着返回值一定要加 const
    constexpr int fun() {
    return 1;
    }

    int main()
    {
    int a = fun();
    }
    上面这个程序是不是就是你说的意思? constexpr 函数可以赋值给 constexpr 标识符,也可以赋值非常量的标识符(如上程序)。
    zzxxisme
        3
    zzxxisme  
       155 天前   ❤️ 1
    @amiwrong123
    > 所以,std::move 只有这一个实现也能正常工作,因为它什么类型都可以接,是吧。附言里我加的这个问题,为什么有了万能引用版本的函数,还能调用到左值引用版本的函数呀?有点不理解了,老哥

    这里有两点吧。一个是 _Ty&& 的那个的确能接住所有类型。另一个是,当你还写了一个 _Ty& 版本的函数的情况下,因为 main 函数里面的 fun_test(a)传进去的是 a 的引用,也就是传进去的是 A&,这个情况下,编译器会认为 _Ty& 版本的 fun_test 会比 _Ty&& 版本的 fun_test 有更高的 rank (优先级)去匹配这个 A&。所以正确的说法我觉得是,_Ty&&能够接住所有类型,但是在有其他重载的情况下,_Ty&&不一定有更高的 rank 被用上。你可以看看 https://en.cppreference.com/w/cpp/language/overload_resolution 的 Ranking of implicit conversion sequences 下列举的不同情况。

    > 上面这个程序是不是就是你说的意思? constexpr 函数可以赋值给 constexpr 标识符,也可以赋值非常量的标识符(如上程序)
    对的。你也可以看看这里最下面给的一些例子: https://en.cppreference.com/w/cpp/language/constexpr
    zzxxisme
        4
    zzxxisme  
       155 天前   ❤️ 1
    再插一句,如果你把第二个 fun_test 改成
    ```
    template <class _Ty>
    void fun_test(_Ty _Arg) { // 这里把&去掉
    cout << "left" << endl;
    }
    ```
    那么 fun_test(_Ty&&)和 fun_test(_Ty)在匹配 fun_test(a)的时候,编译器会选不出来而报错,因为这两个函数面对 A&的 rank 一样。
    amiwrong123
        5
    amiwrong123  
    OP
       154 天前
    @zzxxisme
    在 vs2019 里看了一下,确实 std::move 就只有一个实现
    jackchenly
        6
    jackchenly  
       154 天前
    尝试回答
    constexpr 好像是告诉编译器,要在编译期推导。
    回答完毕
    littlewing
        7
    littlewing  
       154 天前
    _Ty& _Arg 优先级比 _Ty&& _Arg 高
    amiwrong123
        8
    amiwrong123  
    OP
       154 天前
    @zzxxisme #1
    @jackchenly #6
    @littlewing #7
    ```cpp
    #include <iostream>
    using namespace std;

    class A {};

    A returnTemp() { return A(); }

    void test(const A& x) { cout << "left" << endl; }

    void test(A&& x) { cout << "right" << endl; }

    int main()
    {
    auto&& c = returnTemp();//万能引用,推断为 A&&
    test(std::move(c));

    return 0;
    }

    ```
    auto&& c 这里也是万能引用,按照万能引用的说法,returnTemp 函数返回一个 A 类型,A 类型经过万能引用推导,应该也是 A 类型呀?
    但是我经过了 vs2019 debug 后,发现 c 的类型为 A&&,这是为什么呀?
    zzxxisme
        9
    zzxxisme  
       154 天前   ❤️ 1
    > 但是我经过了 vs2019 debug 后,发现 c 的类型为 A&&,这是为什么呀?

    这个不太清楚怎么回答。我觉得因为你是用 auto&&去接 returnTemp()产生的临时变量,所以 c 的类型就是 A&&。如果你使用 auto 去接,那 c 的类型就是 A 。但我觉得不管 c 的类型是 A&&还是 A ,它都是维持着 A 类对象的一份 local copy ,用起来都是一样。

    > _Ty& _Arg 优先级比 _Ty&& _Arg 高

    这个我觉得是有条件的。如果参数本身是 A&这样的引用类型,那_Ty& _Arg 有更高的优先级。如果参数是 A 或者 A&&这样的,_Ty&&有更高的优先级。
    littlewing
        10
    littlewing  
       153 天前
    @amiwrong123
    auto&& c = returnTemp();//万能引用,推断为 A&&
    的原因是函数返回值都是右值
    littlewing
        11
    littlewing  
       153 天前
    @zzxxisme
    > _Ty& _Arg 优先级比 _Ty&& _Arg 高
    这个说法是针对你的例子,因为你传进去的参数是左值引用,所以 Ty& _Arg 比 _Ty&& _Arg 优先级更高
    littlewing
        13
    littlewing  
       153 天前
    > _Ty& _Arg 优先级比 _Ty&& _Arg 高
    应该改成,在你传入 左值引用的时候,_Ty& _Arg 的 匹配度比 _Ty&& _Arg 更高,所以选择了前者
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1391 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 23:33 · PVG 07:33 · LAX 16:33 · JFK 19:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.