- 作者: 雪山肥鱼
- 时间: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;
}
- 返回类型为double. 必须得创建对象
才能判断。 - 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
本质是 返回 函数类型的 && 然后根据后续的可变参数,进行实例化调用。
- T_F: int(*)(int, int)类型
- 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 就返回声明类型,随便传声明函数,声明类型的都可以。这就是模板的魅力啊。
网友评论