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