C++ 模板类型的原样转发和可变参数

原样转发的意义

前文我们实现了一个my_move函数,用来模拟stl的move操作,实现去引用的功能。其内部的原理就是通过remove_reference实现去引用操作。
有时我们也需要保留原类型的左值或者右值属性,进行原样转发,此时就要用forward实现转发功能。
我们先定义一个模板函数

template <typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2)
{
    f(t2, t1);
}

flip1内部调用了函数f
我们写一个函数测试


void ftemp(int v1, int &v2)
{
    cout << v1 << " " << ++v2 << endl;
}

void use_ftemp(){
    int j = 100;
    int i = 99;
    flip1(ftemp, j, 42);
    cout << "i is " << i << " j is " << j << endl;
}

通过打印发现i和j的值没有变化,因为ftemp的v2参数虽然是引用,但是是flip1的形参t1的引用
t1只是形参,修改t1并不能影响外边的实参j。
想要达到修改实参的目的,需要将flip1的参数修改为引用,我们先实现修改后的版本flip2

template <typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
    f(t2, t1);
}

我们定义了一个flip2函数,t1和t2分别是右值引用类型。接下来用一个测试函数进行测试

int j = 100;
int i = 99;
flip2(ftemp, j, 42);
cout << "i is " << i << " j is " << j << endl;

这次我们发现j被修改了,因为flip2的t1参数类型为T1的右值引用,当把实参j赋值给flip2时,T1变为int&,
t1的类型就是int& &&,通过折叠t1变为int&类型。这样t1就和实参j绑定了,在flip2内部修改t1,就达到了修改j的目的。
但是flip2同样存在一个问题,如果flip2的第一个参数f,如果f是一个接受右值引用参数的函数,会出现编译错误。
为说明这一点,我们实现一个接纳模板参数右值引用类型的函数

void gtemp(int &&i, int &j)
{
    cout << "i is " << i << " j is " << j << endl;
}

此时如果我们将gtemp作为参数传递给flip2会报错

int j = 100;
int i = 99;
// flip2(gtemp, j, 42) 会报错
// 因为42作为右值纯递给flip2,t2会被折叠为int&类型
// t2传递给gtemp第一个参数时,int&&无法绑定int&类型
//flip2(gtemp, i, 42);
cout << "i is " << i << " j is " << j << endl;

当我们将42传递给flip2第二个参数时,T2被实例化为int类型,t2就变为int && 类型,通过折叠t2变为int&类型。
t2作为参数传递给gtemp的第一个参数时会报错,
cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
因为t2是一个左值,右值无法绑定该左值。
解决的办法就是实现一个flip函数,内部实现对T2,T1类型的原样转发。

template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}

通过forward将t2类型转化为和T2类型一样的类型,也就是int的右值类型,接下来的调用就不会出问题了

void use_ftemp()
{
    int j = 100;
    int i = 99;
    flip(gtemp, i, 42);
    cout << "i is " << i << " j is " << j << endl;
}

模板的可变参数

模板同样支持可变参数

//可变参数的函数模板
template <typename T>
ostream &print(ostream &os, const T &t)
{
    return os << t; //输出最后一个元素
}

template <typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args &...rest)
{
    os << t << ", ";
    return print(os, rest...);
}

Args是可变的模板参数包, 然后再用Args定义rest变量,这是一个可变参数列表。
我们的模板函数print内部调用stl的print函数,通过对rest…实现展开操作。
调用过程可按如下的方式

void use_printtemp()
{
    int i = 100;
    string s = "hello zack!!!";
    print(cout, i, s, 42);
}

第一次调用print实际是调用的可变参数的print,之后才调用没有可变参数的print函数。

总结

本文介绍了模板类型的原样转发,以及多模板参数列表的使用。
视频链接https://www.bilibili.com/video/BV1ES4y187Yc/?vd_source=8be9e83424c2ed2c9b2a3ed1d01385e9
源码链接 https://gitee.com/secondtonone1/cpplearn

### C++ 中传递数组作为函数参数的方法 在 C++ 编程语言中,可以通过多种方式将数组作为函数参数进行传递。以下是几种常见的方法及其特点: #### 方法一:通过指针传递 当我们将数组名作为实参传递给函数时,实际上传递的是指向该数组第一个元素的指针。因此,在定义形参时可以使用 `const int*` 或类似的语法来接收这个指针。 ```cpp void print(const int *arr, size_t length) { for (size_t i = 0; i < length; ++i) { std::cout << arr[i] << ' '; } } ``` 这种方法的优点在于简单易懂,并且能够有效防止数组退化[^1]。然而需要注意的是,单独使用这种方式无法获取原始数组的实际大小,通常需要额外提供长度参数。 #### 方法二:模拟数组形式声明形参 虽然严格意义上讲,C++ 不支持按值传递整个数组,但我们可以在函数签名中采用类似于数组的形式书写形参列表。这并不会改变底层机制——仍然只是传入了一个指针;但它有助于表达程序员希望操作对象是一个固定尺寸或者可变尺寸的一维数据集合这样的语义信息。 ```cpp void displayArrayElements(const int array[]) { // 假设我们知道具体有多少项要处理... } // 调用示例 displayArrayElements(myIntArray); ``` 这里值得注意的是如果指定某个特定数量比如 `[10]`, 它更多是用来做文档记录用途而非强制约束条件. #### 方法三:利用引用避免数组衰减 为了阻止数组被转换成指针(即所谓的"array decay"), 可以考虑运用引用技术。下面的例子展示了如何创建接受定长整数序列并保持其原样性的接口: ```cpp template<size_t N> void processFixedSizedArray(int (&arr)[N]) { std::cout << "Size of passed array is: " << N << "\n"; /* ... */ } ``` 此模板版本允许编译器自动推导出输入容器的确切规格而无需手动指定任何附加细节[^2]. 此外还有如下非模版实现针对已知规模情况下的解决方案: ```cpp void fun(int (&p)[7]) { cout << "Modified size of array by " "passing by reference: "; cout << sizeof(p) / sizeof(*p) << endl; } ``` 上述代码片段中的 `int(&p)[7]` 表达了一种特殊的绑定关系,它表明变量 p 是一个对七个连续存储单元组成的实体所建立起来的名字别称, 这样做的好处是可以直接访问到真正的数组元素除了首地址之外的信息. 综上所述,C++ 提供了丰富的选项让用户可以根据需求灵活选择最合适的方案来进行数组参数化的管理.[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恋恋风辰

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值