C++ 变长参数解包

[TOC]
最近,看C++11相关的东西,看到模板变长参数的时候,关于变长参数的代码都看不懂了~

变长参数模板

解包的正确姿势

参考[1][3]中说变长参数模板如何解包的问题,其中一种方法是用逗号表达式+初始化列表的方式,参考[3]

template 
void printarg(T t)
{
   cout << t << endl;
}

template 
void expand(Args... args)
{
   int arr[] = {(printarg(args), 0)...};
}

expand(1,2,3,4);

其中 {(printarg(args), 0)…}
展开为 (printarg(arg1), 0), (printarg(arg2), 0), (printarg(arg3), 0), (printarg(arg4), 0)

如果,我把调用改成 expand(1, 2.987, 3, “abc”);
,结果为

1
2.987
3
abc

解包的错误姿势

参考[2]代码

template 
T func(T value)
{
    return value;
}
template 
void g(T value, U ...u)
{
    func(u)...; // error C3520: “u”: 必须在此上下文中扩展参数包
}

g(1.2, 3.4, 5.6);

可以看到,用法 func(u)…
显示的编译错误

error C3520: “u”: 必须在此上下文中扩展参数包

参考[2]中的有网友给出了一些解决办法

int a[] = {0, (func(u), 0)...};

小结

这里我不懂的地方都是属于变长参数范围,总的来说是这个点自己不清楚。所以,看代码就很疑惑。关于我不懂的地方,现在清楚了一点

  • 1.这里的(args)…,就是一个 模式
    后面跟着冒号的表达式,表示对这个 模式
    进行解包
  • 2.解包这个语法使用有限定环境,如果不在限定环境内使用,就会报错 必须在此上下文中扩展参数包

变长参数解包

参考[4][5],这篇非常值得拜读!还有些地方虽然没看懂,但是收获颇丰,基本上我的疑惑都是从这里得到解答了。
列两个模板变长参数解包相关的环境
1. Lambda captures

template
void f(Args... args) {
    auto lm = [&, args...] { return g(args...); };
    lm();
}

2. Brace-enclosed initializers

解包的正确姿势 中就是属于这种情况
参考[1]中的代码也属于这种情况。开始以为是lambda那种情况,细细分析一下觉得还是属于初始化列表环境。下面是逐行解析:

template
auto printf3(T value, Ts... args) {
    std::cout << value << std::endl;
    (void) std::initializer_list{([&args] {
        std::cout << args << std::endl;
    }(), value)...};
}

(1)保证环境正确:在*std::initializer_list *后面的花括号环境中,可以使用包展开。
(2)包展开语法:模式…,这里的模式是小括号中的部分。一个lambda表达式和value组成的逗号表达式

([&args] {
        std::cout << args << std::endl;
    }(), value)

(3)假设三个参数展开为: ([&arg1] {std::cout « arg1 « std::endl;}(), value), ([&arg2] {std::cout « arg2 « std::endl;}(), value), ([&arg3] {std::cout « arg3 « std::endl;}(), value)

这也就是我最终认为,这个例子里面用到的是初始化列表展开,不是属于lambda环境展开。因为初始化列表展开之后到lambda捕获的时候已经不是一个pack了,而是展开之后逐个的元数了。

参考

[1] modern-cpp-tutorial
[2] C++可变参数模板展开
[3] 泛化之美–C++11可变模版参数的妙用
[4] Parameter pack
[5] 形参包