当前位置: 首页 > news >正文

C++ std::forwardT 的使用

C++ std::forward 的使用

C++真实一门细节比较多的语言,稍不注意就会出现奇怪请琢磨不透的bug,这时候就说明你的C++基础不扎实。

C++ lvalue rvalue

std::string hello = "Hello World";

顾名思义左值就是等号左边的hello, 右值就是等号右边的字符串Hello World;
观察一下左值指向了右值,左值指向的右值是可以替换的。左值代表一个具体的存储位置,右值代表一个临时的值.

左值右值的重载函数

class Person {
private:std::string name;
public:// 构造函数Person(const std::string& name) : name(name) {std::cout << "Person constructed: " << name << std::endl;}// 析构函数~Person() {std::cout << "Person destructed: " << name << std::endl;}// 拷贝构造函数Person(const Person& other) : name(other.name) {std::cout << "Person copy-constructed: " << name << std::endl;}// 移动构造函数Person(Person&& other) noexcept : name(std::move(other.name)) {std::cout << "Person move-constructed: " << name << std::endl;}// 赋值运算符Person& operator=(const Person& other) {if (this != &other) {name = other.name;std::cout << "Person copy-assigned: " << name << std::endl;}return *this;}// 移动赋值运算符Person& operator=(Person&& other) noexcept {if (this != &other) {name = std::move(other.name);std::cout << "Person move-assigned: " << name << std::endl;}return *this;}const std::string& getName() const { return name; }void setName(const std::string& newName) { name = newName; }friend std::ostream& operator<<(std::ostream& os, const Person& person) {os << "Person(Name: " << person.name << ")";return os;}
};void consume(Person& p){std::cout << "consume(Person&): " << p << " (lvalue overload)\n";   
}void consume(const Person& p){std::cout << "consume(const Person&): " << p << " (lvalue overload)\n";   
}void consume(Person&& p) {std::cout << "consume(Person&&): " << p << " (rvalue overload)\n";
}void consume(const Person&& p) {std::cout << "consume(const Person&&): " << p << " (rvalue overload)\n";
}

存在普通重载函数是不能存在左值、右值重载函数的。即:

void consume(Person p){std::cout << "consume(Person): " << p << " (lvalue overload)\n";   
}

触发重载函数:

Person alice("Alice");
consume(alice);
// consume(std::string("Alice")); // 1

代码1处使用到了隐式转换, 如果需要禁止使用explicit修饰构造函数. 当然隐式转换只能一次。比如const char*-> std::string -> Person就不支持。
可以尝试删除上面四个重载函数,什么时候继续会执行,什么时候会编译报错。

函数模板

最简单的函数模板

template<typename T>
T add(T a, T b){return a + b;
}

使用C++20 Concepts
加一些限制

template<typename T>
requires requires(T a, T b) { { a + b } -> std::convertible_to<T>; }
T add(T a, T b){return a + b;
}struct Point {int x;int y;friend std::ostream& operator<<(std::ostream& os, const Point& point){os << "Point(" << point.x << ", " << point.y << ")";return os;}Point operator+(const Point& other) const {return Point{x + other.x, y + other.y};}
};void learn001() {int a = 1;int b = 2;Point p1{1, 2};Point p2{3, 4};std::cout << add(a, b) << "\n";std::cout << add(p1, p2) << "\n";
}

当然也可以使用C++11/C++14 的 SFINAE,当然其较为复杂。

TODO: 出一篇简单解释 C++20 ConceptsC++11/C++14 的 SFINAE的博客

requires requires(T a, T b) { { a + b } -> std::convertible_to<T>;
}

{ a + b } -> std::convertible_to<T>;是一个lambda.
a + b: 检查是否可以进行相加操作;
std::convertible_to<T>: 表示相加的结果是否可以隐式转换为T

std::forward<T>的作用

std::forward<T>的源码:

template <typename T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {return static_cast<T&&>(t);
}
class Person {
private:std::string name;
public:// 构造函数Person(const std::string& name) : name(name) {std::cout << "Person constructed: " << name << std::endl;}// 析构函数~Person() {std::cout << "Person destructed: " << name << std::endl;}// 拷贝构造函数Person(const Person& other) : name(other.name) {std::cout << "Person copy-constructed: " << name << std::endl;}// 移动构造函数Person(Person&& other) noexcept : name(std::move(other.name)) {std::cout << "Person move-constructed: " << name << std::endl;}// 赋值运算符Person& operator=(const Person& other) {if (this != &other) {name = other.name;std::cout << "Person copy-assigned: " << name << std::endl;}return *this;}// 移动赋值运算符Person& operator=(Person&& other) noexcept {if (this != &other) {name = std::move(other.name);std::cout << "Person move-assigned: " << name << std::endl;}return *this;}const std::string& getName() const { return name; }void setName(const std::string& newName) { name = newName; }friend std::ostream& operator<<(std::ostream& os, const Person& person) {os << "Person(Name: " << person.name << ")";return os;}
};void consume(Person& p){std::cout << "consume(Person&): " << p << " (lvalue overload)\n";   
}void consume(Person&& p) {std::cout << "consume(Person&&): " << p << " (rvalue overload)\n";
}template<typename T>
void wrapper_forward(T&& arg) { // arg is a universal reference (万能引用) 虽然类型是右值类型,但是arg是一个左值std::cout << "wrapper_forward(T&&): called\n";// consume(std::forward<T>(arg)); // 1// consume(std::forward<T&>(arg)); // error// consume(std::forward<T&&>(arg)); // errorconsume(arg);
}template<typename T>
void wrapper_forward(T& arg) {std::cout << "wrapper_forward(T&): called\n";// consume(arg);consume(std::forward<T>(arg)); // 2// consume(std::forward<T&>(arg)); // error// consume(std::forward<T&&>(arg)); // error
}void learn001() {Person alice("alice");wrapper_forward(alice); // 3
}

试图触发wrapper_forward重载函数, 并试着注释或者打开代码1、2,观察结果。
我解释一下执行过程:
传入左值alice,调用重载函数wrapper_forward(T&), T被推断为Person,所以代码2处
consume(std::forward<T>(arg));consume(std::forward<Person>(arg));
arg类型是Person&
带入std::forward<T>源码如下:

Person&& forward(Person& t) noexcept {return static_cast<Person&&>(t);
}

折叠规律(万能引用):

折叠 结果
T& && T&

举个例子:


void consume(Person& p){std::cout << "consume(Person&): " << p << " (lvalue overload)\n";   
}void consume(Person&& p) {std::cout << "consume(Person&&): " << p << " (rvalue overload)\n";
}template<typename T>
void wrapper_forward(T&& arg) { // arg is a universal reference (万能引用) 虽然类型是右值类型,但是arg是一个左值std::cout << "wrapper_forward(T&&): called\n";consume(std::forward<T>(arg));// consume(std::forward<T&>(arg)); // error// consume(std::forward<T&&>(arg)); // error// consume(arg);
}template <typename T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {return static_cast<T&&>(t);
}void learn001() {Person alice("alice");wrapper_forward(alice); // 1wrapper_forward(std::move(alice)); // 2
}
  1. T 被推断为 Person&,所以wrapper_forward(Person& &&),根据折叠规律结果为Person&, 之后 强转为Person&;
  2. T 被推断为 Person&&,所以wrapper_forward(Person&& &&),根据折叠规律结果为Person&&, 之后 强转为Person&&;

代码验证

template<typename T>
void wrapper_forward(T&& arg) { std::cout << "wrapper_forward(T&): called\n";std::cout << "T is Person?   " << std::is_same_v<T, Person> << "\n";std::cout << "T is Person&?  " << std::is_same_v<T, Person&> << "\n";std::cout << "T is Person&&?   " << std::is_same_v<T, Person&&> << "\n";std::cout << "arg type is Person?   " << std::is_same_v<decltype(arg), Person> << "\n";std::cout << "arg type is Person&?  " << std::is_same_v<decltype(arg), Person&> << "\n";std::cout << "arg type is Person&&?   " << std::is_same_v<decltype(arg), Person&&> << "\n";std::cout << "param type: " << (std::is_lvalue_reference_v<decltype(arg)> ? "lvalue ref" : "rvalue ref") << "\n";consume(std::forward<T>(arg));// consume(std::forward<T&>(arg)); // error// consume(std::forward<T&&>(arg)); // error// consume(arg);
}template <typename T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {return static_cast<T&&>(t);
}void learn001() {Person alice("alice");wrapper_forward(std::move(alice));
}
http://www.hskmm.com/?act=detail&tid=34528

相关文章:

  • tryhackme-预安全-网络基础知识-数据包和帧-07
  • 迈向零信任存储:基于RustFS构建内生安全的数据架构
  • 如果这就是人类脑海的话 雪白纸上划出血红层层痕迹 不如杀死这些记忆
  • 嗣澳——扫,墨依奥——描,希伊桉——线
  • 服务器被攻击!原因竟然是他?真没想到...
  • 得到的眼泪学会了哭泣 得到的悲伤缓慢摧残肉体 被所爱之人踩在地
  • 框架架构的多维赋能——论其对自然语言处理深层语义分析的影响与启示
  • 使用 robocopy 命令备份还原数据速度统计
  • 顺天地之自然
  • Mac 打开终端方式
  • PWN手的成长之路-20-cgpwn2
  • 树状数组和线段树基础
  • C++ofstream写文件bug
  • Debian13中使用Virtual-box安装Window10虚拟机并设置USB直通
  • 2024长城杯决赛-溯源取证1
  • [Agent] ACE(Agentic Context Engineering)和Dynamic Cheatsheet学习笔记
  • 2025年9月模拟赛整合
  • 软工问题总结10.19
  • AI元人文构想研究:理论溯源、跨学科审视与技术路径探析
  • NOAI官方学术支持
  • 【ARM CoreLink 系列 4.1 -- NI-700 interconnect hub 控制器详细介绍】
  • NPM(更新中)
  • 使用DAO模式改造学生信息管理系统
  • 【ARM CoreLink 系列 4 -- NIC-400 控制器详细介绍】
  • Linux反弹shell解析
  • 2025-10-18 MX-S 模拟赛 赛后总结【MX】
  • P1854 花店橱窗布置 解题笔记
  • P1896[SCOI2005]互不侵犯 解题笔记
  • habse
  • hbase