std::initializer_list语法疑问:逗号运算符与Tuple参数适配
我在阅读一段处理任意函数的代码时,对其中initializer_list<int>{((get_arg<Tuple, I>(ds, t)), 0)...}的写法感到困惑。虽然已经知道这和逗号运算符有关,但仍不清楚这种语法适配Tuple参数的原理——看起来std::initializer_list始终用0初始化元素,那Tuple相关的操作是怎么生效的?
相关代码
template<typename Tuple, std::size_t... I> Tuple FunctionHandler::get_args(DataStream & ds, std::index_sequence<I...>) { Tuple t; initializer_list<int>{((get_arg<Tuple, I>(ds, t)), 0)...}; return t; } template<typename Tuple, std::size_t Id> void FunctionHandler::get_arg(DataStream & ds, Tuple & t) { ds >> std::get<Id>(t); }
补充:对逗号运算符的初步理解
已经了解逗号运算符的基本行为:
int a = 1,2; // a的值是1(这里的逗号是变量声明分隔符,不是逗号运算符) int b = (1,2); // b的值是2(逗号运算符:先执行左边表达式,返回右边表达式的值)
原理解释
这段代码的核心是参数包展开和逗号运算符的组合使用,目的是遍历Tuple的所有元素并完成数据读取,具体拆解如下:
参数包展开的触发:
std::index_sequence<I...>是C++11引入的工具,它会生成一个从0开始的整数序列(比如Tuple有3个元素,I...就是0,1,2)。...是参数包展开运算符,它会把包内的每个元素代入到前面的表达式中,生成多个逗号分隔的表达式。逗号运算符的作用:
对于每个I,((get_arg<Tuple, I>(ds, t)), 0)的执行逻辑是:- 先执行左边的
get_arg<Tuple, I>(ds, t):这个函数会通过std::get<Id>(t)访问Tuple的第I个元素,然后用ds >>把数据流中的数据读取到该元素里(这是真正的业务逻辑)。 - 由于
get_arg是void返回类型,逗号运算符会先完成左边函数的执行,然后返回右边的0——这个0只是为了满足std::initializer_list<int>对元素类型的要求,本身没有业务意义。
- 先执行左边的
为什么用initializer_list?
C++的参数包展开需要一个合法的语法上下文来触发,而std::initializer_list的初始化列表刚好提供了这个上下文:它要求元素是逗号分隔的列表,编译器会自动把参数包展开成((get_arg<...,0>(...)),0), ((get_arg<...,1>(...)),0), ...这样的形式,从而逐个执行每个get_arg调用。
如果直接写(get_arg<Tuple, I>(ds, t))...,编译器找不到合法的展开上下文,会报错;而借助initializer_list,我们既满足了语法要求,又间接完成了Tuple所有元素的遍历处理。
内容的提问来源于stack exchange,提问作者ConnellyM

