13

C++ 变长参数解包

 3 years ago
source link: http://pkxpp.github.io/2021/01/09/C -变长参数解包/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[TOC]

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

变长参数模板

解包的正确姿势

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

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

template <class ...Args>
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 <typename T>
T func(T value)
{
    return value;
}
template <typename T, typename ... U>
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<class ...Args>
void f(Args... args) {
    auto lm = [&, args...] { return g(args...); };
    lm();
}

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

template<typename T, typename... Ts>
auto printf3(T value, Ts... args) {
    std::cout << value << std::endl;
    (void) std::initializer_list<T>{([&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] 形参包


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK