恶搞一下std::forward函数
文章目录
- 前言
- 函数模板
- forwawrd 函数定义
- forwawrd 完美转发
- 完美转发失效
- 总结
前言
关于 std::forward
的用法在之前的文章 《C++11中std::move和std::forward到底干了啥》已经总结过了,它被称为完美转发函数,用于函数模板中完成参数转发任务,当形参为左值引用时把它转发成左值,而在形参成为右值引用时把它转发成右值,依靠了引用折叠规则和 std::remove_reference
模板。
前段时间看到std::forward
的源代码时突然有发现有些疑问,后来弄明白了决定换个花样试一试,不过在“恶搞”这个函数之前,先来看一看使用模板的规则,我们以模板函数为例,看看模板是怎么用的。
函数模板
template<class T>
T Add(T a, T b)
{return a + b;
}
这是一个非常简单的模板函数,直接传入参数就可以调用这个函数做加法运算,就像下面这样:
#include <iostream>template<class T>
T Add(T a, T b)
{return a + b;
}int main(void)
{std::cout << Add(2020, 2) << std::endl;std::cout << Add(3.0, 2.1) << std::endl;std::cout << Add(std::string("happy"), std::string(" holiday")) << std::endl;return 0;
}
函数的运行结果如下:
2022
5.1
happyholiday
我们在调用模板函数时虽然没有指定模板 T
的类型,但是编译器会自动推导,分别生成以下三个函数:
int Add(int a, int b)
{return a + b;
}double Add(double a, double b)
{return a + b;
}std::string Add(std::string a, std::string b)
{return a + b;
}
但是当我们采用以下的方式调用函数的时候就会出现编译错误
std::cout << Add(3, 2.1) << std::endl;
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testt.cpp --std=c++11
testt.cpp: In function ‘int main()’:
testt.cpp:13:28: error: no matching function for call to ‘Add(int, double)’std::cout << Add(3, 2.1) << std::endl;^
testt.cpp:5:3: note: candidate: template<class T> T Add(T, T)T Add(T a, T b)^
testt.cpp:5:3: note: template argument deduction/substitution failed:
testt.cpp:13:28: note: deduced conflicting types for parameter ‘T’ (‘int’ and ‘double’)std::cout << Add(3, 2.1) << std::endl;^
编译器给出的错误很明显,那就是没有匹配 Add(int, double)
的函数生成,这个模板只提供了一个类型参数,遇到这种情况应该怎么办呢?我们知道 int 可以隐式转换成 double 类型,那就让它默认生成一个类型为 double 的模板函数 Add(int, double)
就可以了,所以这种情况下把调用函数写成下面这样就可以成功编译了:
std::cout << Add<double>(3, 2.1) << std::endl;
通过这个例子我们发现有些情况下,这个模板函数的参数类型必须显式传递,接下来我们再来熟悉一下 std::forward
函数。
forwawrd 函数定义
先来复习一下函数的定义:
/*** @brief Forward an lvalue.* @return The parameter cast to the specified type.** This function is used to implement "perfect forwarding".*/template<typename _Tp>constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type& __t) noexcept{ return static_cast<_Tp&&>(__t); }/*** @brief Forward an rvalue.* @return The parameter cast to the specified type.** This function is used to implement "perfect forwarding".*/template<typename _Tp>constexpr _Tp&&forward(typename std::remove_reference<_Tp>::type&& __t) noexcept{static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"" substituting _Tp is an lvalue reference type");return static_cast<_Tp&&>(__t);}
以上两个模板函数就是用来实现完美转发左值引用和右值引用的,那么你可以试试,当调用第一个函数的时候能不能推导出 _Tp
是什么类型,我之前的疑惑也在这里,这个函数的参数 typename std::remove_reference<_Tp>::type& __t
和模板参数类型 _Tp
看起来关系很密切,但好像又没关系,因为虽然知道参数类型 typename std::remove_reference<_Tp>::type& __t
是个左值引用,但是你并不知道 _Tp
是什么类型,它还是需要显式来指定的,我们接下来试一试和我们想的一不一样。
forwawrd 完美转发
直接拿一个之前写过的完美转发例子吧,代码如下:
#include <iostream>
#include <utility>void Print(int& val)
{std::cout << "lvalue refrence: val=" << val << std::endl;
}void Print(int&& val)
{std::cout << "rvalue refrence: val=" << val << std::endl;
}template<typename T>
void TPrint(T&& t)
{return Print(std::forward<T>(t));
}int main()
{int date = 2022;TPrint(date);TPrint(501);return 0;
}
编译运行之后的结果如下:
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
lvalue refrence: val=2022
rvalue refrence: val=501
通过结果我们发现 std::forward
函数在拥有万能引用参数的模板函数中实现了完美转发,左值转发后调用了参数为左值引用的函数,右值转发后调用了参数为右值引用的函数,这时如果我们把调用 std::forward
的地方改一下,去掉指定的参数类型 T
,写成 return Print(std::forward(t));
,然后编译看看会发生什么
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
testf.cpp: In instantiation of ‘void TPrint(T&&) [with T = int&]’:
testf.cpp:23:16: required from here
testf.cpp:17:30: error: no matching function for call to ‘forward(int&)’return Print(std::forward(t));^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,from /usr/include/c++/5/bits/stl_algobase.h:64,from /usr/include/c++/5/bits/char_traits.h:39,from /usr/include/c++/5/ios:40,from /usr/include/c++/5/ostream:38,from /usr/include/c++/5/iostream:39,from testf.cpp:1:
/usr/include/c++/5/bits/move.h:76:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&)forward(typename std::remove_reference<_Tp>::type& __t) noexcept^
/usr/include/c++/5/bits/move.h:76:5: note: template argument deduction/substitution failed:
testf.cpp:17:30: note: couldn't deduce template parameter ‘_Tp’return Print(std::forward(t));^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,from /usr/include/c++/5/bits/stl_algobase.h:64,from /usr/include/c++/5/bits/char_traits.h:39,from /usr/include/c++/5/ios:40,from /usr/include/c++/5/ostream:38,from /usr/include/c++/5/iostream:39,from testf.cpp:1:
/usr/include/c++/5/bits/move.h:87:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&&)forward(typename std::remove_reference<_Tp>::type&& __t) noexcept^
/usr/include/c++/5/bits/move.h:87:5: note: template argument deduction/substitution failed:
testf.cpp:17:30: note: couldn't deduce template parameter ‘_Tp’return Print(std::forward(t));^
testf.cpp:17:33: error: return-statement with a value, in function returning 'void' [-fpermissive]return Print(std::forward(t));^
testf.cpp: In instantiation of ‘void TPrint(T&&) [with T = int]’:
testf.cpp:24:15: required from here
testf.cpp:17:30: error: no matching function for call to ‘forward(int&)’return Print(std::forward(t));^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,from /usr/include/c++/5/bits/stl_algobase.h:64,from /usr/include/c++/5/bits/char_traits.h:39,from /usr/include/c++/5/ios:40,from /usr/include/c++/5/ostream:38,from /usr/include/c++/5/iostream:39,from testf.cpp:1:
/usr/include/c++/5/bits/move.h:76:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&)forward(typename std::remove_reference<_Tp>::type& __t) noexcept^
/usr/include/c++/5/bits/move.h:76:5: note: template argument deduction/substitution failed:
testf.cpp:17:30: note: couldn't deduce template parameter ‘_Tp’return Print(std::forward(t));^
In file included from /usr/include/c++/5/bits/stl_pair.h:59:0,from /usr/include/c++/5/bits/stl_algobase.h:64,from /usr/include/c++/5/bits/char_traits.h:39,from /usr/include/c++/5/ios:40,from /usr/include/c++/5/ostream:38,from /usr/include/c++/5/iostream:39,from testf.cpp:1:
/usr/include/c++/5/bits/move.h:87:5: note: candidate: template<class _Tp> constexpr _Tp&& std::forward(typename std::remove_reference<_From>::type&&)forward(typename std::remove_reference<_Tp>::type&& __t) noexcept^
/usr/include/c++/5/bits/move.h:87:5: note: template argument deduction/substitution failed:
testf.cpp:17:30: note: couldn't deduce template parameter ‘_Tp’return Print(std::forward(t));^
testf.cpp:17:33: error: return-statement with a value, in function returning 'void' [-fpermissive]return Print(std::forward(t));
这次出了一个很长的编译错误,看来还是需要指定类型的,既然是需要指定的,那我们指定成其他的有没有问题呢?比如写成下面这样:
return Print(std::forward<float>(t));
编译运行结果如下,都变成了右值引用:
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
rvalue refrence: val=2022
rvalue refrence: val=501
再改成下面这样:
return Print(std::forward<T&>(t));
编译运行结果如下,都变成了左值引用:
albert@home-pc:/mnt/d/data/cpp/testtemplate$ g++ testf.cpp --std=c++11
albert@home-pc:/mnt/d/data/cpp/testtemplate$ ./a.out
lvalue refrence: val=2022
lvalue refrence: val=501
完美转发失效
上面的这两个例子能说明完美转发失效了吗?这倒也不能说明,第一个例子全都转发成了右值引用,第二个例子全部转发成了左值引用,和我们指定的类型是一致的,也算实现了完美转发,只不过通过这些例子更加深入的理解了完美转发的含义,就是能保证转化成指定的类型,如果指定的类型是个万能引用,就会根据原始类型来完成转发,本次探索之旅到此也就结束了,解答疑惑是个有趣的事情。
总结
std::forward
的本质还是进行强制类型转换,它会把传入的参数转发成指定的类型- 完美转发其实是可以脱离左值右值概念的,这也是对完美转发更加深入的理解吧
==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==
每一点付出终有回报,每一滴汗水从不会白流,可能你看不见也摸不着,但其实它已经悄然声息的改变了你,改变了你周围的点点滴滴~
恶搞一下std::forward函数相关推荐
- 完美转发std::forward、引用折叠与函数模板实际上是一场内存“权力的游戏”
简介 学习C++的过程中,会遇到各种技术,一种技术的出现往往是在很多其它技术的铺垫下出现的.对于某项技术的学习,如果不连贯往往导致一知半解,似懂非懂.首先,需明确完美转发是干嘛的?其作用是函数模板 ...
- std:forward 完美转发
概述: // TEMPLATE CLASS identity template<class _Ty> struct identity { // map _Ty ...
- [C/C++]关于C++11中的std::move和std::forward
http://blog.sina.com.cn/s/blog_53b7ddf00101p5t0.html std::move是一个用于提示优化的函数,过去的c++98中,由于无法将作为右值的临时变量从 ...
- 【C++ Primer | 16】std::move和std::forward、完美转发
右值引用应该是C++11引入的一个非常重要的技术,因为它是移动语义(Move semantics)与完美转发(Perfect forwarding)的基石: 移动语义:将内存的所有权从一个对象转移到另 ...
- C++ std::move/std::forward/完美转发
右值引用相关的几个函数:std::move, std::forward 和 成员的 emplace_back; 通过这些函数我们可以避免不必要的拷贝,提高程序性能. move 是将 对象的状态 或者 ...
- std::forward理解
首先说明就是std::forward主要是用来解决在参数传递的过程当中,右值被传递为左值,失去了原来的无拷贝功能. 首先看下面例子: void myp(int &t) {std::cout & ...
- 理解std::move和std::forward
std::move c++11中提供了std::move()来将左值转换为右值引用,从而方便的使用移动语义.move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝. ...
- 《Effective Modern C++》学习笔记之条款二十五:针对右值引用实施std::move,针对万能引用实施std::forward
我们知道,一个函数接受一个右值引用参数后将变成左值(可以对其取地址),所以如果在函数内部想要继续使用其右值属性,就可以对其实施std::move,将形参左值转换为右值. 而对于万能引用,因为其实参可能 ...
- 【从零学Python】什么时候调用forward()函数、图片预处理、return中的if...else...
1.什么时候调用forward()函数 因为Module类是nn模块里提供的一个模型构造类,是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型. import torch from torc ...
最新文章
- R语言dplyr包获取dataframe分组聚合的最大值实战(Maximum Value by Group)
- Collection接口的常用方法
- [置顶] 推荐一款好用的jquery弹出层插件——wbox
- redhat 挂载 iso文件 提示 mount :not a directory
- 小b和回文数(51Nod-2483)
- Go语言---并发编程goroutine
- 深圳很适合创业,无论小白造梦,或是落魄重生
- ## CSP 201412-2 Z字形扫描(C语言)(100分)
- 美国发布新的安全备忘录,提升关键基础设施的网络安全
- C++_homework_StackSort
- BZOJ3376: [Usaco2004 Open]Cube Stacking 方块游戏
- 【Codeforces Round #291 (Div. 2) D】R2D2 and Droid Army【线段树+二分】
- 红米AirDots无线蓝牙耳机连接win10笔记本
- Python_yield_实战应用_读取大文件
- python如何读取dbf文件_Python如何读取DBF文件
- 最合理的关键词密度是多少?
- 消失点:Fast and Accurate Vanishing Point Detection in Complex Scenes
- i.MX RT1064-EVK开发板中基于LPC4322JET100的Freelink调试电路简介
- 使用CSS展现出各行星轨道
- pytorch自动下载的权重保存位置
热门文章
- 过来看~/(≧▽≦)/~啦啦啦!!各种书本课后答案!——第二部分:【化学物理】
- 41 SD配置-销售凭证设置-销售单据到销售单据的复制控制
- 【离散数学】计算主析取范式(基于真值表)
- 从XData大数据一体机看曙光转型
- NB-IoT标准及其介绍
- 2020.04.15软件构造听课笔记
- 浪潮服务器性能测试,内存、IO子系统性能测试_浪潮服务器_服务器评测与技术-中关村在线...
- 随机生成编号与按规则生成编号
- 如何成为“一分钟经理人”
- Halcon shape_trans