declval

作者: 404Not_Found | 来源:发表于2022-02-16 07:21 被阅读0次
    • 作者: 雪山肥鱼
    • 时间:20220214 22:37
    • 目的: 类模板中可变参数的逐步展开
    # 基本概念和常规范例
    # std::declval 为什么返回右值引用类型
      ## 返回类型本身不是好的
      ## 返回左值引用还是返回右值引用
      ## 调用引用限定符修饰的成员函数范例
    # 推导函数返回值范例
    

    基本概念和常规范例

    std::declval -- c++11 新标准中出现的函数模板,没有函数体。(只有声明,没有实现)无法被调用。一般与 decltype, sizeof等关键字配合来进行类型推导、占用内存空间计算等。

    template<class _Ty,
        class = void>
        struct _Add_reference
        {   // add reference
        using _Lvalue = _Ty;
        using _Rvalue = _Ty;
        };
    
    template<class _Ty>
        struct add_rvalue_reference
        {   // add rvalue reference
        using type = typename _Add_reference<_Ty>::_Rvalue;
        };
    
    template<class _Ty>
        using add_rvalue_reference_t = typename _Add_reference<_Ty>::_Rvalue;
    
        // FUNCTION TEMPLATE declval
    template<class _Ty>
        add_rvalue_reference_t<_Ty> declval() noexcept;
    

    类模板add_rvalue_reference的作用:给进来一个类型,能够返回该类型的右值引用类型。
    a) 给int 返回 int&&
    b) 给int & 返回 int&,给引用就是返回一个引用类型。引用折叠。reference-collapsing rule &&& -> &
    c) 给int&& 返回 int&&

    std::declval<T>(注意这里传入的是T,而非T&, T&&)的功能:返回某个类型T 的右值引用,不管该类型是否有默认构造该类型是否可以创建对象(创建对象即必须有构造函数),就能造出一个右值引用。这些动作都是在编译时期完成的。即编译性工具。

    举例:

    class A {
    public:
        A(int i) {
            printf("A::A()函数执行了,this = %p\n", this);
        }
    
        double myfunc() {
            printf("A::myfunc()函数执行了,this = %p\n", this);
            return 12.1;
        }
    };
    
    int main(int argc, char **argv) {
        using YT = decltype(std::declval<A>());//不要丢到declval<A>() 后的括号,因为是函数嘛,否则代码含义发生变化
    
        using boost::typeindex::type_id_with_cvr;
        cout << "YT = " << type_id_with_cvr<YT>().pretty_name() << endl;//显式YT类型
    
        return 0;
    }
    
    图片.png
    • 想获得普通函数myfunc()的返回值类型,老式方法:
    class A {
    public:
        A(int i) {
            printf("A::A()函数执行了,this = %p\n", this);
        }
    
        double myfunc() {
            printf("A::myfunc()函数执行了,this = %p\n", this);
            return 12.1;
        }
    };
    
    int main(int argc, char **argv) {
    
        //想获得普通函数 myfunc的返回值类型
        A myobj(1);//创建对象
        using boost::typeindex::type_id_with_cvr;
        cout << "返回值类型:= " << type_id_with_cvr<decltype(myobj.myfunc())>().pretty_name() << endl;
        
    
        return 0;
    }
    
    1. 返回类型为double. 必须得创建对象
      才能判断
    2. decltype()并没有调用myfunc(),就能知道返回值类型。(private 就获取不到拉)。
    • 不创造类A对象,还能获取到myfunc的返回值。
    class A {
    public:
        A(int i) {
            printf("A::A()函数执行了,this = %p\n", this);
        }
    
        double myfunc() {
            printf("A::myfunc()函数执行了,this = %p\n", this);
            return 12.1;
        }
    };
    
    int main(int argc, char **argv) {
        using boost::typeindex::type_id_with_cvr;
        cout << "返回值类型:= " << type_id_with_cvr<decltype(std::declval<A>().myfunc())>().pretty_name() << endl;s
        return 0;
    }
    

    分析:
    测试代码: 只谈编译,不谈链接

        A&& testObj();//看起来是一个函数声明的语法,,可以看成返回了A&& 类型的对象,可以看成类A对象
        testObj();//看起来是调用testObj函数
        //编译没错,但是链接是有问题的。因为testObj 只声明了,但是没有函数体,所以是有问题的。
    
    A && testObj(); <==> std::declval<A>();两者等价
    

    decltype福利来了,以下代码编译链接都没有错。

            A&& testObj();
        // testObj();
           // testObj().myfunc();
        decltype(testObj().myfunc()) testVar;
    

    原因:decltype 并不需要调用 函数。自然就不需要为 testObj() 提供函数体。

    总结std::declval作用:
    a) 从类型转换的角度来看,将任意类型转换成右值引用类型。
    b) 从假象创建出某类型对象的角度来说,配合decltype,另decltype表达式中,不必经过该类型的构造函数,就能使用该类型的成员函数。
    c) declval 不能被调用。只是得到一个 class &&

    std::declval 为什么返回右值引用类型

    返回类型本身是不好的

    我们自己写一个 declval:

    class A {
    public:
        A(int i) {
            printf("A::A()函数执行了,this = %p\n", this);
        }
    
        double myfunc() {
            printf("A::myfunc()函数执行了,this = %p\n", this);
            return 12.1;
        }
    };
    
    template <typename T>
    T mydeclval() noexcept;
    //T & mydeclval() noexcept;
    
    int main(int argc, char **argv) {
        using boost::typeindex::type_id_with_cvr;
        cout << "mydeclval<A>()的返回类型: " << type_id_with_cvr<decltype(mydeclval<A>())>().pretty_name() << endl;
        cout << "mydeclval<A>().myfunc()的返回类型: " << type_id_with_cvr<decltype(mydeclval<A>().myfunc())>().pretty_name() << endl;
    
        return 0;
    }
    
    图片.png

    抛出问题,mydeclval返回T & ,还是返回 T都能取到 成员函数的返回类型。为什么标准库里的declval 返回的是右值呢?

    但是对上述代码进行修改,在类A中增加私有的析构函数

    private:
     ~A() {} 
    

    会报错,我发调用私有的析构函数:
    错误在哪一行呢?

        using boost::typeindex::type_id_with_cvr;
    //未调用myfunc() 非语义限制
        cout << "mydeclval<A>()的返回类型: " << type_id_with_cvr<decltype(mydeclval<A>())>().pretty_name() << endl;
    //调用了myfunc即语义限制
        cout << "mydeclval<A>().myfunc()的返回类型: " << type_id_with_cvr<decltype(mydeclval<A>().myfunc())>().pretty_name() 
    

    语义限制,虽然没有真正的创建对象,但是因为语义限制,似乎是创建了一个假象的临时对象,用临时对象调用myfunc().
    但没有调用myfunc(),就没有语义限制。

    同样出问题的还有sizeof()

    sizeof(mydeclval<A>());
    

    并没有创建真正的对象,但是从语义上来说,是要创建一个假象的临时对象。

    问题出在:返回的是类型本身。导致要遵循语义限制,编译器内部要创建一个临时的假象对象。为了绕开语义限制,在设计mydeclval 返回模板时,就不要返回类型T.
    可以返回左值或者右值 T& ,T&&。
    修改代码为 T& ,T && 就不会报错了。

    返回左值还是右值

    既然返回本身不好,又返回T& T&& 不会报错,那到底返回哪个呢?

    class A {
    public:
        A(int i) {
            printf("A::A()函数执行了,this = %p\n", this);
        }
    
        double myfunc() {
            printf("A::myfunc()函数执行了,this = %p\n", this);
            return 12.1;
        }
    private:
        ~A(){}
    };
    
    template <typename T>
    T & mydeclval() noexcept;
    
    int main(int argc, char **argv) {
        using boost::typeindex::type_id_with_cvr;
        cout << "mydeclval<A>()的返回类型: " << type_id_with_cvr<decltype(mydeclval<A>())>().pretty_name() << endl;
        cout << "mydeclval<A&>()的返回类型: " << type_id_with_cvr<decltype(mydeclval<A&>())>().pretty_name() << endl;
        cout << "mydeclval<A&>()的返回类型: " << type_id_with_cvr<decltype(mydeclval<A&&>())>().pretty_name() << endl;
    
        return 0;
    }
    
    图片.png

    返回左值引用,必出左值引用
    T &

    • 传入 A& => A& & => A&
    • 传入 A&& => A&& & => A&
    • 传入 A => A& => A&

    修改,返回右值引用:

    template <typename T>
    T && mydeclval() noexcept;
    
    图片.png

    遵循折叠规则。
    T &&

    • 传入 A& => A& && => A&
    • 传入 A&& && => A&& && => A&&
    • 传入 A => A&& => A&&

    为什么选择返回右值引用?因为只有当返回右值引用时,才能拿到A&&, 返回左值,无论传进来的A,A &, A&& ,都只能拿到 左值A&。
    能够拿到的类型更为全面。

    调用引用限定符修饰的成员函数范例

    class ALR {
    public:
        void onAnyValue() {
            cout << "ALR::onAnyValue () 函数执行了" << endl;
        }
    
        void onLvalue() & {
            cout << "ALR::onLvalue () & 函数执行了" << endl;
        }
    
        void onRvalue() && {
            cout << "ALR::onRvalue () && 函数执行了" << endl;
        }
    };
    
    template <typename T>
    T && mydeclval() noexcept;
    
    int main(int argc, char **argv) {
        ALR alr;//左值对象
        alr.onLvalue();
        alr.onRvalue();//编译错误, onRvalue 只能被右值调用
    
        ALR().onRvalue();
        ALR().onLvalue();//编译错误 onLvalue 只你能被左值调用
        return 0;
    } 
    

    另外举例:

        decltype(mydeclval<ALR>().onAnyValue());
        decltype(mydeclval<ALR&>().onLvalue());
        decltype(mydeclval<ALR&&>().onRvalue());
    
        decltype(mydeclval<ALR&>().onRvalue());//错误
        decltype(mydeclval<ALR&&>().onLvalue());//错误
    

    推导函数返回值范例

    int myfunc(int a, int b) {
        return a + b;
    }
    
    template <typename T_F, typename... U_Args>
    decltype(declval<T_F>()  (declval<U_Args>()...)) TestFnRtnTmp1(T_F func, U_Args... args) {
        auto rtnvalue = func(args...);
        return rtnvalue;
    }
    
    int main(int argc, char **argv) {
        auto result = TestFnRtnTmp1(myfunc, 5, 8);//func 推断:int(*)(int, int) 5,8 推断出来都是int 类型
        cout << result << endl;
        return 0;
    }
    
    decltype(declval<T_F>()  (declval<U_Args>()...)) 
    

    此行代码解读。实际上就是 declval 根据T_F 推断出,你这个是个函数指针,然后调用后续的可变参,即传入 5,8.从而推断出,T_F 的返回值是 int

    本质是 返回 函数类型的 && 然后根据后续的可变参数,进行实例化调用。

    1. T_F: int(*)(int, int)类型
    2. decltype(...) 是int 类型,即返回值类型
      2.1 decltype(declval<T_F>()) 即:int(*&&)(int, int) 函数指针的右值类型,理解成函数指针就行了。
    int main(int argc, char **argv) {
    
        int i = 10;
        int &&j = std::move(i);
    
        i = 11;
        cout << j << endl;//11
    
    
        return 0;
    }
    
    int(*fp)(int x, int y);
    int(*&&yy_fp)(int x, int y) = std::move(fp);
    fp = myfunc;
    cout<<fp_var(1,2)<<endl;
    

    2.2 decltype(std::declval<U_args>()...) 推出来的是 int&&, int&&
    共同推出 返回值类型为 int.

    //为什么不这么写呢?
    decltype(T_F(U_Args)...)//报错
    

    原因:decltype 用法错误,decltype()中出现的是 变量,对象,表达式,函数名,函数指针等。不可出现类型名,T_F(U_Aargs)... 代表着类型名,即函数返回参数类型。

    declval 可用的原因是,假象创建一个对象出来。

    另一种写法,返回类型后置的写法:

     double myfunc(int a, double b) {
        return a + b;
    }
    
    template <typename T_F, typename... U_Args>
    decltype(declval<T_F>()(declval<U_Args>()...)) TestFnRtnTmp1(T_F func, U_Args... args) {
        auto rtnvalue = func(args...);
        return rtnvalue;
    }
    
    template <typename T_F, typename... U_Args>
    auto TestFnRtnTmp12(T_F func, U_Args... args) -> decltype(func(args...)) {
        auto rtnvalue = func(args...);
        return rtnvalue;
    }
    
    int main(int argc, char **argv) {
        auto result = TestFnRtnTmp1(myfunc, 5, 8.1);//func 推断:int(*)(int, int) 5,8 推断出来都是int 类型
        cout << result << endl;
        return 0;
    }
    

    auto 结合 decltype,进行返回类型后置。

    函数名后跟的,decltype:
    decltype(func(args...));//返回的类型与 func相同
    
    decltype(func);// 返回类型为函数指针。
    

    目的:希望 func 返回声明类型, TestFnRtnImp2 就返回声明类型,随便传声明函数,声明类型的都可以。这就是模板的魅力啊。

    相关文章

      网友评论

          本文标题:declval

          本文链接:https://www.haomeiwen.com/subject/maahlrtx.html