C++ 模板元编程(2)-可变参数

可变参数是指在函数或模板中可以接受任意数量的参数。在C++中,可变参数通常使用模板和递归的方式来实现,允许函数或模板处理不定数量的参数。

例如,在C++标准库中自己实现的max函数,min函数等都是实现一个两个对象比较,但是如果我们想要自己实现多个参数呢?在模板元编程中可变参数也有很多作用。

可变参数的作用是允许函数或模板处理不定数量的参数,从而实现更灵活的函数调用和模板实例化。这样可以简化代码,提高代码的复用性,并且能够处理各种不同数量的参数。

我们首先看第一个例子

递归使用可变参数

void print() {
  
}

template<typename T, typename... Types>
void print(T firstArg, Types... args) {
  cout << firstArg << '\n';
  print(args...);
}

int main() {
  print(1, '1', "b");
}

当程序执行到 print(1, '1', "b"); 这一行时,会调用 print 函数模板的实例化版本,其中 T 被推导为 int,firstArg 被赋值为 1,args 被推导为 char 和 const char* 类型的参数包。

然后,实例化的 print 函数会打印 firstArg 的值,即 1,然后递归调用 print 函数,将剩余的参数包 args 传递给下一个实例化的 print 函数。

递归调用的 print 函数会以同样的方式处理参数包,直到参数包为空,递归结束。

这样,整个过程就实现了对任意数量参数的打印。

那么第一行的print()有什么作用呢?

第一个print()函数是一个基础情况,用于处理可变参数模板中的递归终止条件。当参数包args为空时,递归调用会停止,这时就会调用这个空的print()函数,起到终止递归的作用。

如果我们删除这个print那么编译器就会报错,因为这里采用的是递归的方式,必须有一个基本状态。

那么可以不使用这种方式吗?

折叠表达式使用可变参数

template<typename T, typename... Types>
void print(T firstArg, Types... args) {
  ((cout << firstArg << '\n') , ... , (cout << args << '\n'));
}

在这里我们使用一个折叠表达式来达到一样的效果,

折叠表达式的语法如下:

(expression op ... op expression)

其中,op是折叠操作符,也可以是+、-、*、/等二元运算符,也可以是&&、||等逻辑运算符。注意 ‘ , ’ 是一个特殊的运算符,如果我们使用 (firstArg , ... , args)进行赋值操作,那么被赋值只会等于args,因此,使用 ‘,’ 进行折叠表达式只能进行操作,而不能进行赋值等类似的操作。

使用std::initializer_list处理(单一类型)

#include <iostream>
#include <initializer_list>

void print(std::initializer_list<int> args) {
    for (int arg : args) {
        std::cout << arg << '\n';
    }
}

int main() {
    print({1, 2, 3, 4, 5});
    return 0;
}

使用tuple(可以多类型)

template <typename... Args>
void print(const std::tuple<Args...>& args) {
    std::apply([](const auto&... arg){((std::cout << arg << '\n'), ...);}, args);
}

int main() {
    print(std::make_tuple(1, '1', "b"));
    return 0;
}

注意事项:

可变参数的一个重要特点是需要使用递归、折叠表达式或其他方式来处理参数包。此外,需要注意的特点包括:

1. 参数包的展开:需要使用递归、折叠表达式或其他方式来展开参数包,以便对每个参数进行处理。
2. 参数包的顺序:在处理参数包时,需要注意参数的顺序,确保按照期望的顺序进行处理。
3. 参数包的类型:需要考虑参数包中参数的类型,以便在模板函数或其他函数中正确处理不同类型的参数。

这些特点需要在处理可变参数时特别注意,以确保正确处理任意数量的参数。

如何进行约束概念?

很多时候我们使用可变参数,有时候我们希望约束一个概念和范围。例如我们定义了一个func函数用来求累加,如下代码:

template<typename T, typename ...Types>
auto func(T firstargs,Types... args) {
  return (firstargs + ... + args);
}
int main() {
  cout<<func(1, 3, "1");
}
我们定义了一个func函数,传入1,3,“1”,同时我们运行发现没有报错(vs2022),或者有可能报错,但是在运行阶段出错,而这种错误一旦在项目复杂化之后就跟更难处理和观察,而如果我们能约束概念,规定传入的数据一定是数值型,是不是就能预防这种错误。
C++20提供了概念这个库,我们只需要加上一行  
  requires(is_integral_v<T> && ... && is_integral_v<Types>)

就能预防错误,这里我们的IDE(vs2022)就直接提供了错误信息,尝试运行编译器也会给我们错误信息

同时注意requires必须也得以折叠表达式(或者可以尝试写一个递归的require条件)展开,如果只是对于T进行约束,或者同时约束T和Types,前者只会对第一个参数进行约束,后者会忽略最后一个参数的约束条件(导致错误)。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值