17.变参模板

变参模板

概念

变参模板(Variadic Templates)是 C++ 中的一项特性,允许模板接受可变数量的参数。这意味着你可以创建能够处理不同数量参数的通用模板,而不需要为每种可能的参数数量编写不同的模板。

在 C++11 引入变参模板之前,通常使用函数重载或其他技巧来处理不同数量的参数。引入变参模板后,代码更加灵活、可读性更好,并且通常能够减少代码量。

以下是变参模板的主要概念:

1. 语法

变参模板使用 typename... 或者 class... 来表示可变数量的类型参数,同时使用递归展开来处理参数包。在函数或类模板中,可以通过递归展开的方式对参数包进行操作。

template 
void myFunction(T arg1, Args... args) {
    // 递归展开参数包
    // 处理 arg1
    // 递归调用 myFunction(args...) 处理剩余参数
}

2. 递归展开

递归展开是指在模板实例化的过程中,逐一处理参数包中的每个参数。这通常通过递归函数调用或者展开表达式来实现。

template 
void process(T arg) {
    // 处理单个参数
}

template 
void process(T arg, Args... args) {
    // 处理当前参数 arg
    process(args...); // 递归调用,处理剩余参数
}

3. 基本情况和递归情况

变参模板通常涉及两种情况:基本情况和递归情况。基本情况处理参数包为空的情况,而递归情况处理参数包不为空的情况。

// 基本情况
template 
void process(T arg) {
    // 处理单个参数
}

// 递归情况
template 
void process(T arg, Args... args) {
    // 处理当前参数 arg
    process(args...); // 递归调用,处理剩余参数
}

4. 示例

以下是一个简单的变参模板示例,演示了如何实现可变参数的打印函数:

/*************************************************************************
        > 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::typeARGS...中的第一个类型,typename ARG::rest::typeARGS...中的第二个类型。

​ 实现代码:

/*************************************************************************
        > 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 中使用这个类型链表作为参数类型。

让我们逐步解释代码:

  1. 定义类型链表类模板 ARG

    template
    class ARG {
    public:
        typedef T type;
        typedef ARG rest;
    };
    
    template
    class ARG {
    public:
        typedef T type;
    };
    
    • ARG 类模板接受可变数量的模板参数。在主模板中,定义了 type 成员表示当前参数的类型,并定义了 rest 成员作为递归的下一个类型链表节点。
    • 通过模板的递归定义,实现了类型链表的生成。
    • 基本情况是当只有一个参数时,没有 rest
  2. 定义类模板 Test

    template
    class Test {
    public:
        T operator()(typename ARG::type a, typename ARG::rest::type b) {
            return a + b;
        }
    };
    
    • Test 类模板接受一个主类型 T 和一个类型链表 ARGS
    • operator() 函数使用类型链表 ARGS 中的参数类型进行运算。在这里,它接受两个参数 ab,它们的类型分别来自于 ARG::typeARG::rest::type
    • main 函数中创建了两个 Test 类的实例 t1t2,并分别调用它们。
  3. 主函数:

    int main() {
        Test t1;
        Test t2;
        cout << t1(3, 4) << endl;
        cout << t2(3.5, 4) << endl;
        return 0;
    }
    
    • 创建了两个 Test 类的实例 t1t2,分别使用不同的参数类型。
    • 调用 t1t2operator() 函数,输出结果。

这个代码展示了如何使用模板和类型链表的概念来实现一个可变参数的类模板。

优化1:

​ 实现这段代码只能传三个类型,传入第四个类型会直接报错,也就是说实现一个可以使Test t3;这行代码报错的功能,只修改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别名的。

优化2:

​ 使Test t1;这段代码可以写成Test t1;这种形式。

template class Test {};
template
class Test  {
public:
    T operator()(typename ARG::type a, typename ARG::rest::ftype b) {
        return a + b;
    }
};

​ 直接使用模板的偏特化,就欧克了。

优化3:
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...中的第几个数据类型

优化4:

​ 在不改变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++的宏定义和模板特化来定义了一些简单的元编程结构。下面是对代码的分析:

代码分析
1. 命名空间定义:
BEGINS(sum_test)
// ... sum_test 定义 ...
ENDS(sum_test)

这部分使用宏定义创建了一个名为 sum_test 的命名空间。这是为了封装 sum 结构体,防止与其他代码中的相同名称发生冲突。

2. 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 来计算和。

3. even_test 命名空间:
BEGINS(even_test)
// ... even_test 定义 ...
ENDS(even_test)

这部分使用宏定义创建了一个名为 even_test 的命名空间,用于封装 is_even 结构体。

4. is_even 结构体:
template
struct is_even {
    static constexpr const char *r = N % 2 == 0 ? "yes" : "no";
};

这个结构体用于判断一个整数是否为偶数。通过模板参数 N 的奇偶性来返回相应的字符串。注释掉的部分是原始的版本,使用字符数组比较奇偶性。

5. good_bad_test 命名空间:
BEGINS(good_bad_test)
// ... good_bad_test 定义 ...
ENDS(good_bad_test)

这部分使用宏定义创建了一个名为 good_bad_test 的命名空间,用于封装 score_judge 结构体。

6. score_judge 结构体:
template
struct score_judge {
    static constexpr const char *r = N >= 60 ? "good" : "bad";
};

这个结构体用于判断一个分数是否及格(大于等于60)。根据分数返回相应的字符串,判断通过模板参数 N 的值。

7. is_prime_test 命名空间:
BEGINS(is_prime_test)
// ... is_prime_test 定义 ...
ENDS(is_prime_test)

这部分使用宏定义创建了一个名为 is_prime_test 的命名空间,用于封装 is_prime 结构体。

8. getNext 结构体:
template
struct getNext {
    static constexpr const int r = N % I ? I + 1 : 0;
};

这个结构体用于获取下一个可能的除数。如果 I 不是 N 的因子,那么返回 I + 1,否则返回 0。

9. test 结构体:
template
struct test {
    static constexpr const char *r = I * I > N ? "yes" : test::r, N>::r;
};

这个结构体用于测试是否存在小于等于根号 N 的因子。如果存在,返回 “no”,否则递归调用 getNext::r 获取下一个可能的除数,然后再次测试。

10. 基本情况:
template
struct test<0, N> {
    static constexpr const char *r = "no";
};

这是递归的终止条件,当 I 为0时返回 “no”。

11. is_prime 结构体:
template
struct is_prime {
    static constexpr const char *r = test<2, N>::r;
};

这个结构体用于判断一个整数是否为质数。它通过调用 test<2, N>::r 进行判断,从2开始检查是否有小于等于根号 N 的因子。

你可能感兴趣的:(c++,开发语言,c++)