C++ std::forward<T> 的使用

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)); // error
    consume(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); // 1
    wrapper_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));
}
posted @ 2025-10-19 21:09  爱情丶眨眼而去  阅读(14)  评论(0)    收藏  举报