《深入理解C++11》笔记-变长模板

上一篇:《深入理解C++11》笔记-常量表达式
本篇将介绍C++11中的变长模板,在这之前我们先回顾一下变长函数。

变长函数、变长宏

原来C++支持变长函数以及变长宏,能接收任何长度的参数列表:

// 变长函数
long sum(unsigned int count, ...)
{
    va_list var;
    long sum = 0;
    va_start(var, count);             // va_start读取变长参数列表
    for (unsigned int i = 0; i < count; ++i)
    {
        sum += va_arg(var, int);      // 按照从左到右的顺序取值
    }
    va_end(var);                      // 清空
    return sum;
}

// 变长宏,用__VA_ARGS__取出变长值
#define LOG_OUT(fmt, ...) printf("file[%s]:line[%d], " fmt "", __FILE__, __LINE__, __VA_ARGS__)

int main(void)
{
    LOG_OUT("out %d", sum(5, 1, 2, 3, 4, 5));   // file[xxx]:line[30], out 15

    getchar();
    return 0;
}

可以看到,变长函数能够接收任意个数的参数,但是实际上函数本身无法知道参数的类型,且无法限制传入参数的类型。因此,C++11中就有所谓的变长模板。

变长模板类

C++11中有一个tuple类模板,它的作用和pair类似,不过tuple支持变长参数而pair只支持两个参数。

    std::pair<int, std::string> num_pair;
    num_pair = std::make_pair(1, "one");

    std::tuple<int, std::string, double> num_tuple;
    num_tuple = std::make_tuple(1, "one", 1.0);

tuple的声明是template <typename... Types> class tuple;,可以看到声明中有…省略符表示该模板的参数类型是变长的。Types被称为是”模板参数包”,这是一种新的模板参数类型。有了”模板参数包”,tuple就能接受任意多个参数作为模板参数。与普通模板参数类,模板参数包也可以是非类型的:

template <int... Args> class Example;
Example<1, 2, 3> ex;

一个模板参数包在模板推导时会被认为是模板的单个参数,所以为了使用模板参数包,我们需要将其解包。在C++11中,通常通过”包扩展”的表达式来完成。例如:

template<typename T1, typename T2> class Type {};
template<typename... Types> class Example : private Type<Types ...> {};
Example<int, double> ex;

模板类型参数<int, double>先是被打包成了Types,然后在调用Type模板通过Types ...被还原成了<int, double>。但是这样只能解包两个类型参数的模板,怎么才能实现变长?tuple的实现方式给出了答案:

template<typename... Types> class tuple;               // 变长模板声明
template<typename _This, typename... _Rest>
class tuple<_This, _Rest...>: private tuple<_Rest...>  // 递归的偏特化
{
    _This this_tpye;
};
template<> class tuple<> {};                           // 空参数的偏特化

首先,声明了只包含一个模板参数包的tuple模板类。然后,又定义了一个双参数的偏特化tuple版本,一个是类型模板参数_This,另一个是模板参数包_Rest,同时将包扩展表达式的模板类tuple<_Rest...>作为私有基类,_This作为第一个成员,这样就会引起基类的递归构造。最后,又定义了一个空参数的偏特化版本,作为递归的边界,当模板参数包参数为0个时结束递归。当我们实例化一个类型std::tuple<int, std::string, double> num_tuple;时,先构造出tuple<double>,然后是tuple<std::string, double>,最后完成构造tuple<int, std::string, double>
这里写图片描述
这种变长模板的定义方式有些复杂,不过有效解决了模板参数变长的问题。同样的,该方式也能用于非类型模板类:

template <long... Args> class sum;
template<long _This, long... _Rest>
class sum<_This, _Rest...>
{
public:
    static const long value = _This + sum<_Rest...>::value;
};
template<> class sum<>
{
public:
    static const long value = 0;
};

int main(void)
{
    std::cout << sum<1, 2, 3, 4, 5>::value << std::endl;  // 15,在编译期计算

    return 0;
}

变长模板函数

变长模板函数与变长模板类不同,变长模板函数参数需要声明”函数模板包”。例如:template <typename... Types> void func(T... args);,其中Types是模板参数包,args则是这些参数类型对应的数据,即”函数参数包”,C++11规定函数参数包必须唯一且是函数的最后一个参数。使用这两个概念,就能实现变长函数的功能,且能指定类型:

// 空参数版本,终结模板的递归
void new_printf(const char* s)
{
    while (*s)
    {
        if (*s == '%' && *++s != '%')
        {
            throw std::runtime_error("error");
        }
        else
        {
            std::cout << *s++;
        }
    }
}

// 参数递归
template<typename T, typename... Args>
void new_printf(const char* s, T value, Args... args)
{
    while (*s)
    {
        if (*s == '%' && *++s != '%')
        {
            std::cout << value;                      // value是带类型的变量,和原来的变长函数不同
            return new_printf(++s, args...);
        }
        else
        {
            std::cout << *s++;
        }
    }

    throw std::runtime_error("error");
}

int main(void)
{
    new_printf("test %d, %s\n", 1, "out");                         // 可以使用默认类型
    new_printf("test %c, %s\n", char(65), std::string("out"));     // 可以指定参数类型

    return 0;
}

变长模板-进阶

前面的内容可以看到,模板参数包能在模板类的基类描述中展开,也能在表达式中进行展开。下面列出所有可以展开模板参数包的情况:

  • 表达式
  • 初始化列表
  • 基类描述列表
  • 类成员初始化列表
  • 模板参数列表
  • 通用属性列表
  • lambda函数的捕捉列表

另外,除了正常的包扩展表达式,还可以定义其他包扩展表达式:

// 正常的包扩展表达式
template <typename... Types> class Example: private B<Types...> {};  
// 省略号在尖括号后面的包扩展表达式
template <typename... Types> class Example: private B<Types>... {}; 

以上两种对于同样的实例化Example<X, Y>,解包的方式是不同的:

// 正常的包扩展表达式为多参数
class Example<X, Y>: private B<X, Y>{};
// 省略号在尖括号后面的包扩展表达式为多继承
class Example<X, Y>: private B<X>, private B<Y>{};

变长模板函数也有以上的特点,例如:

template <typename... Types>
void wrapper(Types... types)     // 用于包装my_print
{
}

template <typename Type> 
Type my_print(Type t)
{
    std::cout << t;
    return t;
}

template <typename... Args>
void func(Args... args)
{
    wrapper(my_print(args)...);   
}

int main(void)
{
    func(1, ", ", 1.2);  // my_print(args)...被解包为,my_print(1.2)、my_print(", ")、my_print(1),注意这里的顺序是反的(使用vs2015编译),书中的例子顺序是正的

    getchar();
    return 0;
}

C++11还提供了计算模板参数包中类型个数的方法sizeof…:

template <typename... Types> int args_count(Types... types)
{
    return sizeof...(types);                         // 计算个数
}

int main(void)
{
    std::cout << args_count(1, 1, 1) << std::endl;   // 3

    return 0;
}

变长模板还能作为模板的参数,但是使用起来比较复杂,后续有机会再进行介绍。

下一篇:《深入理解C++11》笔记-原子类型和原子操作

猜你喜欢

转载自blog.csdn.net/wizardtoh/article/details/81059760
今日推荐