• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

C++ 11中的可变模板参数

开发技术 开发技术 6小时前 2次浏览

目录
  • 概述
  • 前言
  • 可变模板参数的语法
    • 函数
    • 非类型模板参数和可变模板参数结合
  • 参数包的展开
    • C++11中的展开方式
      • 递归展开
      • 逗号表达式搭配initializer_list展开
    • C++17中的折叠表达式
      • 一元折叠表达式
      • 二元折叠表达式
      • 如何评价

概述

大家在C++中应该见过不少函数,它们既没有限制参数的类型,也没有限制参数的个数,比如vector<T>::emplace()make_unique<T>()。它们都是利用C++11中的可变模板参数来实现的。对于这一新特性,需要掌握以下三点

  • 可变模板参数的语法
  • 参数包的展开
  • 实践

前言

在讲可变模板参数之前,需要先讲C语言中的变长参数

#include<stdarg.h>
#define END (-1)
int sum(int num, ...)
{
    va_list list;
    va_start(list, num);
    int sum = 0;
    for (int cur = num; cur != END; cur = va_arg(list, int))
        sum += cur;
    va_end(list);
    return sum;
}


int main()
{
    // 输出15
    cout << sum(1, 2, 3, 4, 5, END) << endl;
}

C语言中的va_arg宏并不能判断出哪个参数参数包的末尾,所以只能通过自己设定结束位,并通过显式判断来截取有效的参数

而在C++11中,使用变长参数更简单了(好吧其实也不怎么算变长)

int sum(std::initializer_list<int> initializerList)
{
    int sum = 0;
    for (const int& i : initializerList)
        sum += i;
    return sum;
}

int main()
{
    cout << sum({1, 2, 3, 4, 5}) << endl;
}

可变模板参数的语法

函数

以C++11的标准来看,声明一个可变参数的模板函数有两种方法

template<typename... Ts>
void AnyNumberOfParam(Ts... ars) {}

int main()
{
    // 可以接受0及以上个参数
    AnyNumberOfParam();
    AnyNumberOfParam(1, 2);
    AnyNumberOfParam("824", std::vector<int>(), 3);
}
template<typename T, typename... Ts>
void AnyNumberOfParam(T least, Ts... ars) {}

int main()
{
    // 至少需要有一个参数
    // AnyNumberOfParam();
    AnyNumberOfParam(1, 2);
    AnyNumberOfParam("824", std::vector<int>(), 3);
}

template<typename T, typename... Ts>
class TestTemplateClass {};
template<typename... Ts>
class TestTemplateClass {};

int main()
{
    // 因为支持0及以上的参数 所以这么写是合法的
    TestTemplateClass t;
}

非类型模板参数和可变模板参数结合

参数包的展开

C++11中的展开方式

递归展开

假设我们通过设计一个函数,能逐个输出它的参数

void print()
{
    cout << "empty" << endl;
}
template<typename T, typename... Ts>
void print(T first, Ts... args)
{
    std::cout << first << std::endl;
    print(args...);
}

int main()
{
    print(1, 2, "Mike", 3.21);
    std::cout << std::endl;
    print();
}

以上代码的递归过程为

  • print(1, 2, "Mike", 3.21);
  • print(2, "Mike", 3.21);
  • print("Mike", 3.21);
  • print(3.21);
  • print();

C++ 11中的可变模板参数

通过递归方式展开参数包,当所有参数包展开完毕后,自然为空,所以调用到非模板的递归中止函数

当然还可以使用模板递归中止函数,这种情况就不支持空参数包了

template<typename T>
void print(T end)
{
    cout << end << endl;
}
template<typename T, typename... Ts>
void print(T first, Ts... args)
{
    std::cout << first << std::endl;
    print(args...);
}

int main()
{
    print(1, 2, "Mike", 3.21);
    // print();
}

以上代码的递归过程为

  • print(1, 2, "Mike", 3.21);
  • print(2, "Mike", 3.21);
  • print("Mike", 3.21);
  • print(3.21);

而在C++17中,对递归展开法进行了优化

template<typename T, typename... Ts>
void print(T first, Ts... args)
{
    std::cout << first << std::endl;
    if constexpr(sizeof...(args) > 0)
        print(args...);
}

逗号表达式搭配initializer_list展开

通过递归展开参数包的缺点很明显,需要重载一个递归终止函数,同时还需要判定终止函数是否需要使用到模板,可以说是很不便了,下面介绍逗号表达式结合initializer_list的展开方法

template<typename... Ts>
void print(Ts&&... args)
{
    auto lambda = [](auto&& data) { std::cout << data << std::endl; };
    initializer_list<int> il = { (lambda(std::forward<Ts>(args)), 0)... };
}

int main()
{
    print(1, 2, "Mike", 3.21);
    print();
}

C++17中的折叠表达式

一元折叠表达式

先来看看如何在C++17中利用折叠表达式(...)展开参数包实现一个求平均数的函数

template<typename... T>
int avg(T... args)
{
    return (args + ...) / sizeof...(args);
}
int main()
{
    // 输出26
    cout << avg(1, 2, 5, 'a') << endl;
    // 编译出错
    // cout << avg() << endl;
}

对于一元折叠表达式而言,只有逗号运算符,&&||操作允许空包,其它的如果出现空包则会编译出错

When a unary fold is used with a pack expansion of length zero, only the following operators are allowed:

  1. Logical AND (&&). The value for the empty pack is true

  2. Logical OR (||). The value for the empty pack is false

  3. The comma operator (,). The value for the empty pack is void()

// 一元折叠逗号表达式
template<typename ...Args>
void printer(Args&&... args)
{
    (..., (std::cout << args << std::endl));	// 左折叠 且每个参数间隔输出std::endl
    // ((std::cout << args), ...);	// 右折叠
}

int main()
{
    // (((1 << 2) << Mike) << 3.21) 左折叠
    // 1 n 2 n Mike n 3.21
    printer(1, 2, "Mike", 3.21);
}

二元折叠表达式

二元折叠表达式,支持空包操作

对于std::cout的二元表达式而言,只能使用左折叠(因为输出必须以std::cout开头,而这也就代表了它是左折叠)

template<typename ...Args>
void printer(Args&&... args)
{
    // 二元左折叠
    (std::cout << ... << args);
}

template<typename ... Ts>
void printer_space(Ts&&... args)
{
    auto lambda = [] (auto params)
    {
        cout << ends;
        return params;
    };
    (std::cout << ... << lambda(args));
}

int main()
{
    // 12Mike3.21
    printer(1, 2, "Mike", 3.21);
    // nothing...
    printer();
    //  1 2 Mike 3.21
    printer_space(1, 2, "Mike", 3.21);
}

拓展:以下代码是在参数包展开完毕之后再输出std::endl,而不是每拆一次就输出一次

template<typename ...Args>
void printer(Args&&... args)
{
    (std::cout << ... << args) << std::endl;
}

如何评价

C++ 11中的可变模板参数

Fold expressions with arbitrary callable?


程序员灯塔
转载请注明原文链接:C++ 11中的可变模板参数
喜欢 (0)