C++元编程嵌套包展开:内部省略号为何仅展开ys而非xs?
C++参数包展开的作用域与绑定规则解析
问题背景
在梳理C++元编程知识时,先实现了针对tuple的for_each模板函数:
using namespace std; template<class... As, class F> constexpr auto for_each(tuple<As...> as, F f) { return apply( [](auto... xs){ (f(xs), ... ); }, as ); }
通过嵌套调用for_each,实现了run_all函数:
template <class A, class B> void g(A a, B b); template<class... As, class... Bs> constexpr auto run_all( tuple<As...> as, tuple<Bs...> bs) { return for_each( as, [bs](auto x){ for_each( bs, [x](auto y) { g(x, y); } ); } ); }
手动将for_each内联展开后,得到以下代码:
template <class A, class B> void g(A a, B b); template<class... As, class... Bs> constexpr auto run_all( tuple<As...> as, tuple<Bs...> bs) { return apply( [bs](auto... xs){ ( apply( [xs](auto... ys){ (g(xs, ys), ...); }, bs ) , ...); }, as ); }
核心疑问:内层的省略号为何仅展开ys而不展开xs?是xs因被捕获免受展开,还是参数包存在时不能随意内联展开?
严谨解释
1. 参数包的作用域与绑定规则
参数包的展开完全依赖作用域内的可见性和省略号的绑定对象,和是否被lambda捕获无关:
- 外层lambda的参数包是
xs...,对应模板参数包As...,外层的折叠表达式(..., ...)是对xs...的展开——每一个xs都是As中的单个元素,会触发一次针对bs的apply调用。 - 内层lambda的参数包是
ys...,对应模板参数包Bs...,内层的折叠表达式(g(xs, ys), ...)仅绑定ys...:此时xs是外层lambda传递进来的单个变量(不是参数包),因为外层xs...在展开时,每个迭代的xs都是独立的单个元素,被内层lambda捕获后是普通的单个值,不存在“展开”的前提。
2. 折叠表达式的绑定逻辑
根据C++标准,折叠表达式中的省略号必须与当前上下文可见的唯一参数包绑定(多重折叠除外)。在内层lambda的作用域中,只有ys...是参数包,xs是捕获的非包变量,因此省略号只会关联ys...,不会误触xs。
参考资料
- C++标准:[temp.variadic](可变参数模板)章节,明确了参数包的作用域、展开规则,以及折叠表达式中省略号与参数包的绑定逻辑。
- 书籍:
- 《C++ Templates: The Complete Guide, 2nd Edition》第15章,详细讲解了可变参数模板的展开、作用域与lambda的交互场景。
- 《Effective Modern C++》条款30,涉及可变参数与lambda捕获的细节,可辅助理解参数包的生命周期。
内容的提问来源于stack exchange,提问作者Eric Auld
相关产品推荐
相关产品推荐

