C++11 特性可变参数模板

可变参数模板
本文介绍可变参模板的基本概念与用法,大量参考了cppreferen内容,可以说是对其的整理与总结。

  • 背景
  • 什么是模板的形参与实参?
  • 模板形参包
  • 函数模板的参数包
  • 形参包展开
    • 用法
    • 可展开位置
  • 总结

背景

c++11特性引入可变模板参数这一特性,这使得可以含有不定个数参数的模板类和模板参数得以实现。如下代码:
变参数模板类如下:

// 变参数模板类
template 
struct Tuple {};
Tuple<> t0;           // Types 不包含实参
Tuple t1;        // Types 包含一个实参:int
Tuple t2; // Types 包含二个实参:int 与 float
Tuple<0> error;       // 错误:0 不是类型

变参数模板函数如下:

template 
void f(Types ... args);
f();       // OK:args 不包含实参
f(1);      // OK:args 包含一个实参:int
f(2, 1.0); // OK:args 包含二个实参:int 与 double

那么变参类与变参函数该如何使用呢?这篇文章将带你了变参的解模板类与模板函数,教你如何使用。

什么是模板的形参与实参?

模板参数可以分为形参与实参,类似于函数中的形参与实参:形参是变量声明,实参是变量具体值。模板里形参就是类型(或值)声明,实参是你想传入的类型。可以参考cppreference,简单的说如下:

  • 模板形参
    template < 形参列表 > 声明	
    
  • 模板实参
    Tuple t1;        // Types 包含一个实参:int
    

模板形参包

知道了模板的形参与实参的概念再来理解下形参的包——形参包

模板形参包是接受零或更多模板实参(非类型、类型或模板)的模板形参。函数模板形参包是接受零或更多函数实参的函数形参。至少有一个形参包的模板被称作变参模板。
——cppreference

模板形参包(出现于别名模版、类模板、变量模板及函数模板形参列表中),定义如下:

类型 … Args(可选) (1)
typename|class … Args(可选) (2)
template < 形参列表 > typename(C++17)|class … Args(可选) (3)
——cppreference

  1. 带可选名字的非类型模板形参包
  2. 带可选名字的类型模板形参包
  3. 带可选名字的模板模板形参包

以上是官方的定义,简单的说我们变参的模板类或者模板函数的模板声明可以是如下:

template  // typename ... Types 为形参包
struct Tuple {};

template // typename ... Types 为形参包
void f(Types ... args);

函数模板的参数包

模板函数中的参数该如何写呢?
函数模板参数包定义如下:

Args … args(可选)

如代码:

template 
void f(Types ... args); // Types ... args 为函数模板的参数包

形参包展开

用法

在函数中或类中使用形参包时必须先将其展开。
形式;

模式 …

定义如下:

模式后随省略号,其中至少有一个形参包的名字至少出现一次,其被展开成零或更多个逗号分隔的模式实例,其中形参包的名字按顺序被替换成包中的各个元素。

如下代码:

// 代码1
template void f(Us... pargs) {}
template void g(Ts... args) {
    f(&args...); // “&args...” 是包展开
                 // “&args” 是其模式
}
g(1, 0.2, "a"); // Ts... args 展开成 int E1, double E2, const char* E3
                // &args... 展开成 &E1, &E2, &E3
                // Us... 展开成 int* E1, double* E2, const char** E3

代码中f(&args...)中展开为f(&E1, &E2, &E3), 若改为f(&args)...,则展开为f(&E1),f(&E1),f(&E1),当然这样会编译报错。
又如下代码:

// 代码2
template struct Tuple {};
template struct Pair {};
 
template struct zip {
    template struct with {
        typedef Tuple...> type;
//        Pair... 是包展开
//        Pair 是模式
    };
};
 
typedef zip::with::type T1;
// Pair... 展开成
// Pair, Pair 
// T1 是 Tuple, Pair>

可展开位置

  • 函数实参列表
  • 有括号初始化器
  • 花括号包围的初始化器
  • 模板实参列表
  • 函数形参列表
  • 模板形参列表
  • 基类说明符与成员初始化器列表
  • lamda函数捕捉列表

技巧:递归包展开
可以采用递归调用的方式来定义模板函数

template 
T Sum(T data) {
    return data;
}

template 
T Sum(T data, Args... args) {
    return data + Sum(args...);
}

int main() {
    std::cout << Sum(1, 2, 3, 4) << std::endl;  // 输出 10
    std::cout << Sum(1) << std::endl;   // 输出 1
    return 0;
}

总结

一个变参模板可以有以下步骤

  1. 形参包定义 template
  2. 若是函数模板,函数参数定义 f(Args ... args)
  3. 函数或类内部包展开:f(args...)f(args)...

你可能感兴趣的:(C++)