目录
概述
std::forward是C++11引入的函数模板,它的作用是实现函数参数的完美转发,通俗的讲就是根据传入的参数,决定将参数以左值引用还是右值引用的方式进行转发。
std::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);
- }
完美转发原理
在C++中,存在左值(lvalue)和右值(rvalue)的概念。关于左值和右值的概念,自己可以查询相关资料。简单来说,左值是指可以取地址的、具有持久性的对象,而右值是指不能取地址的、临时生成的对象。传统上,当一个左值传递给一个函数时,参数会以左值引用的方式进行传递;当一个右值传递给一个函数时,参数会以右值引用的方式进行传递。
以VS2019的std::forward源码实现来讲解:
1. 当传递给func函数的实参类型为左值QObject时,T被推导为QObject&类别。然后forward会实例化为std::forward
2. 而当传递给func函数的实参类型为右值QObject时,T被推导为QObject。然后forward被实例化为std::forward
3. 可见,std::forward会根据传递给func函数实参(注意,不是形参)的左/右值类型进行转发。当传给func函数左值实参时,forward返回左值引用,并将该左值转发给doWork。而当传入func的实参为右值时,forward返回右值引用,并将该右值转发给doWork函数。
完美转发失败的情形
在std::forward中,失败的情况其实就是完美转发的目的没有达到,甚至根本无法实现
1、花括号的初始化方式调用
- #include
- #include
-
- void test(const std::array<int,5> ¶m){}
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- std::array<int, 5> arr = {0,1,2};
- func(arr);
- // func({0,1,2,3,4});
- }
解决方案:先用auto声明一个局部变量,再将该局部变量传递给转发函数。
2、0或者NULL做为空指针传递
其实这个也是c++11后强调使用nullptr做为指针的空值的一个重要原因。之所以会这样,是因为0和NULL往往会被默认转成为int类型,这也是在早期的C编译器中经常遇到的一个编译现象,就是“XXX无法转成int”其实就是写错了,但默认就是往int上靠,这个没办法。一如下面:
- void test( void *ptr){}
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- func(0);
- }
解决方案:传递nullptr,而非0或NULL
3、static const的应用(含constexpr)
- void test(int i){}
-
- class Data {
- public:
- static const int d_ = 1;
- };
- //const int Data::d_ ;
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- func(Data::d_);
- }
这段代码在VS中可以成功运行,但在g++中会报一个链接错误“ undefined reference to Data::d_ collect2: error: ld returned 1 exit status”。VS中对编译和链接相对来说还是宽松一些。
想通过连接只需要把注释的部分解开就可以了。
解决方案:在类外定义该成员变量。注意这声变量在声明时一般会先给初始值。因此定义时无需也不能再重复指定初始值
4、对重载和模板的函数名处理
这句话的意思是指在完美转发时如果参数是一个函数名称,那么如果这个函数名称的函数如果存在重载(或是模板)的话,在普通编程的直接调用情况下是没有问题,但是在完美转发时,不管是直接调用名称还是使用模板函数时会存在转发的错误。看下面的代码就理解了
- void myfunc_test(void func(int)) {}
-
- void myfunc(int a) {}
- void myfunc(int a, int b) {}
-
- template<typename T>
- void dotest(T t){}
- template<typename T>
- void func(T&& t)
- {
- myfunc_test(std::forward
(t)); - }
-
- int main()
- {
- // func(myfunc);
- // func(dotest);
- myfunc_test(myfunc);
- }
一般来说,传递函数做为函数参数时,用函数指针的情况很多,但也应该知道,也可以直接传递函数名称而不是指针的情况来操作。上面的代码就是这样,就会出现本节的问题。但是如果直接调用函数myfunc_test而非完美转发,则没有问题。同样,函数模板也是如此,因为函数模板毕竟不是一个实例,而是一组类似实例的泛型。
想要解决这个问题也很简单,依照着普通编译成功的方式,采用指针的方式直接指明调用的函数即可,模板也同样如此。
5、位字段
位域是由机器字的若干任意部分组成的(如32位int的第3至5个比特),但这样的实体是无法直接取地址的。而fwd的形参是个引用,本质上就是指针,所以也没有办法创建指向任意比特的指针。
解决方案:制作位域值的副本,并以该副本来调用转发函数。
- struct xxxxx
- {
- std::uint32_t v: 4,
- I : 4,
- D : 6,
- E : 2,
- t : 16;
- //...
- };
-
- void test(std::uint16_t v) {}
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- xxxxx ip = {};
- test(ip.t); //调用void func(int)
- //func(ip.t); //error,func形参是引用,由于位域是比特位组成。无法创建比特位的引用!
-
- //解决方案:创建位域的副本,并传给func
- auto length = static_cast
uint16_t>(ip.t); - func(length);
- }
实例讲解
实例:
- template <typename _Callable, typename... _Types>
- auto invoke(_Callable&& obj, _Types... argv)
- {
- return _Invoke<_Callable, _Types...>::_Call(
- std::forward<_Callable>(obj),
- std::forward<_Types>(argv)...);
- }
目录
概述
std::forward是C++11引入的函数模板,它的作用是实现函数参数的完美转发,通俗的讲就是根据传入的参数,决定将参数以左值引用还是右值引用的方式进行转发。
std::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);
- }
完美转发原理
在C++中,存在左值(lvalue)和右值(rvalue)的概念。关于左值和右值的概念,自己可以查询相关资料。简单来说,左值是指可以取地址的、具有持久性的对象,而右值是指不能取地址的、临时生成的对象。传统上,当一个左值传递给一个函数时,参数会以左值引用的方式进行传递;当一个右值传递给一个函数时,参数会以右值引用的方式进行传递。
以VS2019的std::forward源码实现来讲解:
1. 当传递给func函数的实参类型为左值QObject时,T被推导为QObject&类别。然后forward会实例化为std::forward
2. 而当传递给func函数的实参类型为右值QObject时,T被推导为QObject。然后forward被实例化为std::forward
3. 可见,std::forward会根据传递给func函数实参(注意,不是形参)的左/右值类型进行转发。当传给func函数左值实参时,forward返回左值引用,并将该左值转发给doWork。而当传入func的实参为右值时,forward返回右值引用,并将该右值转发给doWork函数。
完美转发失败的情形
在std::forward中,失败的情况其实就是完美转发的目的没有达到,甚至根本无法实现
1、花括号的初始化方式调用
- #include
- #include
-
- void test(const std::array<int,5> ¶m){}
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- std::array<int, 5> arr = {0,1,2};
- func(arr);
- // func({0,1,2,3,4});
- }
解决方案:先用auto声明一个局部变量,再将该局部变量传递给转发函数。
2、0或者NULL做为空指针传递
其实这个也是c++11后强调使用nullptr做为指针的空值的一个重要原因。之所以会这样,是因为0和NULL往往会被默认转成为int类型,这也是在早期的C编译器中经常遇到的一个编译现象,就是“XXX无法转成int”其实就是写错了,但默认就是往int上靠,这个没办法。一如下面:
- void test( void *ptr){}
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- func(0);
- }
解决方案:传递nullptr,而非0或NULL
3、static const的应用(含constexpr)
- void test(int i){}
-
- class Data {
- public:
- static const int d_ = 1;
- };
- //const int Data::d_ ;
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- func(Data::d_);
- }
这段代码在VS中可以成功运行,但在g++中会报一个链接错误“ undefined reference to Data::d_ collect2: error: ld returned 1 exit status”。VS中对编译和链接相对来说还是宽松一些。
想通过连接只需要把注释的部分解开就可以了。
解决方案:在类外定义该成员变量。注意这声变量在声明时一般会先给初始值。因此定义时无需也不能再重复指定初始值
4、对重载和模板的函数名处理
这句话的意思是指在完美转发时如果参数是一个函数名称,那么如果这个函数名称的函数如果存在重载(或是模板)的话,在普通编程的直接调用情况下是没有问题,但是在完美转发时,不管是直接调用名称还是使用模板函数时会存在转发的错误。看下面的代码就理解了
- void myfunc_test(void func(int)) {}
-
- void myfunc(int a) {}
- void myfunc(int a, int b) {}
-
- template<typename T>
- void dotest(T t){}
- template<typename T>
- void func(T&& t)
- {
- myfunc_test(std::forward
(t)); - }
-
- int main()
- {
- // func(myfunc);
- // func(dotest);
- myfunc_test(myfunc);
- }
一般来说,传递函数做为函数参数时,用函数指针的情况很多,但也应该知道,也可以直接传递函数名称而不是指针的情况来操作。上面的代码就是这样,就会出现本节的问题。但是如果直接调用函数myfunc_test而非完美转发,则没有问题。同样,函数模板也是如此,因为函数模板毕竟不是一个实例,而是一组类似实例的泛型。
想要解决这个问题也很简单,依照着普通编译成功的方式,采用指针的方式直接指明调用的函数即可,模板也同样如此。
5、位字段
位域是由机器字的若干任意部分组成的(如32位int的第3至5个比特),但这样的实体是无法直接取地址的。而fwd的形参是个引用,本质上就是指针,所以也没有办法创建指向任意比特的指针。
解决方案:制作位域值的副本,并以该副本来调用转发函数。
- struct xxxxx
- {
- std::uint32_t v: 4,
- I : 4,
- D : 6,
- E : 2,
- t : 16;
- //...
- };
-
- void test(std::uint16_t v) {}
-
- template<typename T>
- void func(T&& t)
- {
- test(std::forward
(t)); - }
- int main()
- {
- xxxxx ip = {};
- test(ip.t); //调用void func(int)
- //func(ip.t); //error,func形参是引用,由于位域是比特位组成。无法创建比特位的引用!
-
- //解决方案:创建位域的副本,并传给func
- auto length = static_cast
uint16_t>(ip.t); - func(length);
- }
实例讲解
实例:
- template <typename _Callable, typename... _Types>
- auto invoke(_Callable&& obj, _Types... argv)
- {
- return _Invoke<_Callable, _Types...>::_Call(
- std::forward<_Callable>(obj),
- std::forward<_Types>(argv)...);
- }
评论记录:
回复评论: