变参模板(Variadic Templates)是 C++ 中的一项特性,允许模板接受可变数量的参数。这意味着你可以创建能够处理不同数量参数的通用模板,而不需要为每种可能的参数数量编写不同的模板。
在 C++11 引入变参模板之前,通常使用函数重载或其他技巧来处理不同数量的参数。引入变参模板后,代码更加灵活、可读性更好,并且通常能够减少代码量。
以下是变参模板的主要概念:
变参模板使用 typename...
或者 class...
来表示可变数量的类型参数,同时使用递归展开来处理参数包。在函数或类模板中,可以通过递归展开的方式对参数包进行操作。
template
void myFunction(T arg1, Args... args) {
// 递归展开参数包
// 处理 arg1
// 递归调用 myFunction(args...) 处理剩余参数
}
递归展开是指在模板实例化的过程中,逐一处理参数包中的每个参数。这通常通过递归函数调用或者展开表达式来实现。
template
void process(T arg) {
// 处理单个参数
}
template
void process(T arg, Args... args) {
// 处理当前参数 arg
process(args...); // 递归调用,处理剩余参数
}
变参模板通常涉及两种情况:基本情况和递归情况。基本情况处理参数包为空的情况,而递归情况处理参数包不为空的情况。
// 基本情况
template
void process(T arg) {
// 处理单个参数
}
// 递归情况
template
void process(T arg, Args... args) {
// 处理当前参数 arg
process(args...); // 递归调用,处理剩余参数
}
以下是一个简单的变参模板示例,演示了如何实现可变参数的打印函数:
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:[email protected]
> Created Time: Sun Nov 12 22:47:44 2023
************************************************************************/
#include
using namespace std;
class A {
public:
A(int x, int y) : x(x), y(y) {}
int x, y;
};
ostream &operator<<(ostream &out, const A &a) {
out << a.x << ' ' << a.y;
return out;
}
template
void Print(T a) {
cout << a << endl;
return ;
}
template
void Print(T a, ARGS... args) {
cout << a << ' ';
Print(args...);
return ;
}
int main() {
A a(5, 6);
Print("hello world");
Print("hello world", 3, 3.4, a);
Print(a, 7.3, "xiao");
return 0;
}
template
void Print(T a, ARGS... args) {
cout << a << ' ';
Print(args...);
return ;
}
这个类模板不断递归输出。
template
void Print(T a) {
cout << a << endl;
return ;
}
提供一个结束条件,也就是重载一个模板,终止递归。
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:[email protected]
> Created Time: Sun Nov 12 22:47:44 2023
************************************************************************/
#include
using namespace std;
template
class Test {
public:
T operator()(typename ARG::type a, typename ARG::rest::type b) {
return a + b;
}
};
int main() {
Test t1;
Test t2;
cout << t1(3, 4) << endl;
cout << t2(3.5, 4) << endl;
return 0;
}
实现一个可以满足如上代码的工具类ARG
,满足typename ARG
是ARGS...
中的第一个类型,typename ARG
是ARGS...
中的第二个类型。
实现代码:
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:[email protected]
> Created Time: Sun Nov 12 22:47:44 2023
************************************************************************/
#include
using namespace std;
template
class ARG {
public:
typedef T type;
typedef ARG rest;
};
template
class ARG {
public:
typedef T type;
};
template
class Test {
public:
T operator()(typename ARG::type a, typename ARG::rest::type b) {
return a + b;
}
};
int main() {
Test t1;
Test t2;
cout << t1(3, 4) << endl;
cout << t2(3.5, 4) << endl;
return 0;
}
这段代码定义了一个类模板 ARG
,该模板用于生成类型链表,然后在类模板 Test
中使用这个类型链表作为参数类型。
让我们逐步解释代码:
定义类型链表类模板 ARG
:
template
class ARG {
public:
typedef T type;
typedef ARG rest;
};
template
class ARG {
public:
typedef T type;
};
ARG
类模板接受可变数量的模板参数。在主模板中,定义了 type
成员表示当前参数的类型,并定义了 rest
成员作为递归的下一个类型链表节点。rest
。定义类模板 Test
:
template
class Test {
public:
T operator()(typename ARG::type a, typename ARG::rest::type b) {
return a + b;
}
};
Test
类模板接受一个主类型 T
和一个类型链表 ARGS
。operator()
函数使用类型链表 ARGS
中的参数类型进行运算。在这里,它接受两个参数 a
和 b
,它们的类型分别来自于 ARG::type
和 ARG::rest::type
。main
函数中创建了两个 Test
类的实例 t1
和 t2
,并分别调用它们。主函数:
int main() {
Test t1;
Test t2;
cout << t1(3, 4) << endl;
cout << t2(3.5, 4) << endl;
return 0;
}
Test
类的实例 t1
和 t2
,分别使用不同的参数类型。t1
和 t2
的 operator()
函数,输出结果。这个代码展示了如何使用模板和类型链表的概念来实现一个可变参数的类模板。
实现这段代码只能传三个类型,传入第四个类型会直接报错,也就是说实现一个可以使Test
这行代码报错的功能,只修改ARG
工具类实现。
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:[email protected]
> Created Time: Sun Nov 12 22:47:44 2023
************************************************************************/
#include
using namespace std;
template
class ARG {
public:
typedef T type;
typedef ARG rest;
};
template
class ARG {
public:
typedef T ftype;
};
template
class Test {
public:
T operator()(typename ARG::type a, typename ARG::rest::ftype b) {
return a + b;
}
};
int main() {
Test t1;
Test t2;
cout << t1(3, 4) << endl;
cout << t2(3.5, 4) << endl;
return 0;
}
只需要更改一下别名就欧克了。这样当传入的参数不是三个的时候,是找不到ftype
别名的。
使Test
这段代码可以写成Test
这种形式。
template class Test {};
template
class Test {
public:
T operator()(typename ARG::type a, typename ARG::rest::ftype b) {
return a + b;
}
};
直接使用模板的偏特化,就欧克了。
class Test {
public:
T operator()(typename ARG<1, ARGS...>::type a, typename ARG<2, ARGS...>::type b) {
return a + b;
}
};
实现一个ARG
类,要求:传入一个整数x
表示ARGS...
中的第几个数据类型
实现:
template
class ARG {
public:
typedef ARG type;
};
template
class ARG<1, T> {
public:
typedef T type;
};
template
class ARG<1, T, ARGS...> {
public:
typedef T type;
};
此时我们就实现了要求:传入一个整数x
表示ARGS...
中的第几个数据类型
在不改变ARG
类的情况下,如何控制传入的参数数量:下面是我的实现方式:
/*************************************************************************
> File Name: test.cpp
> Author:Xiao Yuheng
> Mail:[email protected]
> Created Time: Sun Nov 12 22:47:44 2023
************************************************************************/
#include
using namespace std;
template
class ARG {
public:
typedef ARG type;
};
template
class ARG<1, T> {
public:
typedef T type;
};
template
class ARG<1, T, ARGS...> {
public:
typedef T type;
};
template
struct A {
static constexpr int r = A::r + 1;
};
template
struct A {
static constexpr int r = 1;
};
template
struct Zero {
typedef int no;
};
template<>
struct Zero<2> {
typedef int yes;
};
template class Test;
template
class Test {
public:
typedef typename Zero::r>::yes TYPE_NUM_2;
T operator()(typename ARG<1, ARGS...>::type a, typename ARG<2, ARGS...>::type b) {
return a + b;
}
};
int main() {
Test q;
return 0;
}
定义了一个A
类,来统计传入ARGS...
里面有多少个参数,定义了一个Zero
类,来判断是不是两个。(感觉很麻烦,啊哈哈哈哈哈哈)
实现如下主函数的所有功能:
/*************************************************************************
> File Name: 20.cpp
> Author:Xiao Yuheng
> Mail:[email protected]
> Created Time: Mon Nov 13 11:26:02 2023
************************************************************************/
#include
using namespace std;
#define BEGINS(x) namespace x {
#define ENDS(x) }
BEGINS(sum_test)
int main() {
cout << sum<5>::r << endl;
cout << sum<7>::r << endl;
cout << sum<100>::r << endl;
return 0;
}
ENDS(sum_test)
BEGINS(even_test)
int main() {
cout << is_even<5>::r << endl;
cout << is_even<6>::r << endl;
return 0;
}
ENDS(even_test)
BEGINS(good_bad_test)
int main() {
cout << score_judge<60>::r << endl;
cout << score_judge<54>::r << endl;
return 0;
}
ENDS(good_bad_test)
BEGINS(is_prime_test)
int main() {
cout << is_prime<2>::r << endl;
cout << is_prime<3>::r << endl;
cout << is_prime<5>::r << endl;
cout << is_prime<5>::r << endl;
cout << is_prime<9973>::r << endl;
return 0;
}
ENDS(is_prime_test)
int main() {
sum_test::main();
even_test::main();
good_bad_test::main();
is_prime_test::main();
return 0;
}
/*************************************************************************
> File Name: 20.cpp
> Author:Xiao Yuheng
> Mail:[email protected]
> Created Time: Mon Nov 13 11:26:02 2023
************************************************************************/
#include
using namespace std;
#define BEGINS(x) namespace x {
#define ENDS(x) }
BEGINS(sum_test)
template
struct sum {
static constexpr int r = sum::r + N;
};
template<>
struct sum<1> {
static constexpr int r = 1;
};
int main() {
cout << sum<5>::r << endl;
cout << sum<7>::r << endl;
cout << sum<100>::r << endl;
return 0;
}
ENDS(sum_test)
BEGINS(even_test)
/*
template
struct is_even {
static constexpr const char *r = is_even::r[0] == 'n' ? "yes" : "no";
};
template<>
struct is_even<1> {
static constexpr const char *r = "no";
};
*/
template
struct is_even {
static constexpr const char *r = N % 2 == 0 ? "yes" : "no";
};
int main() {
cout << is_even<5>::r << endl;
cout << is_even<6>::r << endl;
return 0;
}
ENDS(even_test)
BEGINS(good_bad_test)
template
struct score_judge {
static constexpr const char *r = N >= 60 ? "good" : "bad";
};
int main() {
cout << score_judge<60>::r << endl;
cout << score_judge<54>::r << endl;
return 0;
}
ENDS(good_bad_test)
BEGINS(is_prime_test)
template
struct getNext {
static constexpr const int r = N % I ? I + 1 : 0;
};
template
struct test {
static constexpr const char *r = I * I > N ? "yes" : test::r, N>::r;
};
template
struct test<0, N> {
static constexpr const char *r = "no";
};
template
struct is_prime {
static constexpr const char *r = test<2, N>::r;
};
int main() {
cout << is_prime<2>::r << endl;
cout << is_prime<3>::r << endl;
cout << is_prime<5>::r << endl;
cout << is_prime<5>::r << endl;
cout << is_prime<103>::r << endl;
return 0;
}
ENDS(is_prime_test)
int main() {
sum_test::main();
even_test::main();
good_bad_test::main();
is_prime_test::main();
return 0;
}
这段代码使用了C++的宏定义和模板特化来定义了一些简单的元编程结构。下面是对代码的分析:
BEGINS(sum_test)
// ... sum_test 定义 ...
ENDS(sum_test)
这部分使用宏定义创建了一个名为 sum_test
的命名空间。这是为了封装 sum
结构体,防止与其他代码中的相同名称发生冲突。
sum
结构体:template
struct sum {
static constexpr int r = sum::r + N;
};
template<>
struct sum<1> {
static constexpr int r = 1;
};
这个结构体用于计算从1到N的整数的和。它是一个递归模板结构,其中基本情况 sum<1>
定义了递归的终止条件,而一般情况 sum
则通过递归调用 sum
来计算和。
even_test
命名空间:BEGINS(even_test)
// ... even_test 定义 ...
ENDS(even_test)
这部分使用宏定义创建了一个名为 even_test
的命名空间,用于封装 is_even
结构体。
is_even
结构体:template
struct is_even {
static constexpr const char *r = N % 2 == 0 ? "yes" : "no";
};
这个结构体用于判断一个整数是否为偶数。通过模板参数 N
的奇偶性来返回相应的字符串。注释掉的部分是原始的版本,使用字符数组比较奇偶性。
good_bad_test
命名空间:BEGINS(good_bad_test)
// ... good_bad_test 定义 ...
ENDS(good_bad_test)
这部分使用宏定义创建了一个名为 good_bad_test
的命名空间,用于封装 score_judge
结构体。
score_judge
结构体:template
struct score_judge {
static constexpr const char *r = N >= 60 ? "good" : "bad";
};
这个结构体用于判断一个分数是否及格(大于等于60)。根据分数返回相应的字符串,判断通过模板参数 N
的值。
is_prime_test
命名空间:BEGINS(is_prime_test)
// ... is_prime_test 定义 ...
ENDS(is_prime_test)
这部分使用宏定义创建了一个名为 is_prime_test
的命名空间,用于封装 is_prime
结构体。
getNext
结构体:template
struct getNext {
static constexpr const int r = N % I ? I + 1 : 0;
};
这个结构体用于获取下一个可能的除数。如果 I
不是 N
的因子,那么返回 I + 1
,否则返回 0。
test
结构体:template
struct test {
static constexpr const char *r = I * I > N ? "yes" : test::r, N>::r;
};
这个结构体用于测试是否存在小于等于根号 N
的因子。如果存在,返回 “no”,否则递归调用 getNext::r
获取下一个可能的除数,然后再次测试。
template
struct test<0, N> {
static constexpr const char *r = "no";
};
这是递归的终止条件,当 I
为0时返回 “no”。
is_prime
结构体:template
struct is_prime {
static constexpr const char *r = test<2, N>::r;
};
这个结构体用于判断一个整数是否为质数。它通过调用 test<2, N>::r
进行判断,从2开始检查是否有小于等于根号 N
的因子。