这个可变参数模板代码有什么作用?

NoS*_*tAl 16 c++ templates variadic-templates c++11

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
    [](...){}((f(std::forward<Args>(args)), 0)...); 
}
Run Code Online (Sandbox Code Playgroud)

它最近在isocpp.org上有特色,没有任何解释.

Yak*_*ont 25

简短的回答是"它做得不是很好".

它调用f每个args...,并丢弃返回值.但它确实会以不必要的方式在许多情况下导致意外行为.

代码没有排序保证,如果f给定的返回值Arg有重载,operator,它可能会产生不幸的副作用.

有一些空白区域:

[](...){}(
  (
    f(std::forward<Args>(args)), 0
  )...
);
Run Code Online (Sandbox Code Playgroud)

我们将从内部开始.

f(std::forward<Args>(args))是一个不完整的陈述,可以用a扩展....它将在扩展时调用f其中一个args.请致电此声明INVOKE_F.

(INVOKE_F, 0)取值f(args),operator,然后应用0.如果返回值没有覆盖,f(args)则会丢弃返回值并返回a 0.叫这个INVOKE_F_0.如果f返回带有覆盖的类型,则operator,(int)此处会发生错误,如果该运算符返回非POD类型,则稍后可以获得"有条件支持"的行为.

[](...){}创建一个lambda,它将C风格的variadics作为唯一的参数.这与C++ 11参数包或C++ 14可变参数lambda不同.将非POD-esque类型传递给...函数可能是非法的.叫这个HELPER

HELPER(INVOKE_F_0...)是参数包扩展.在调用HELPER's 的背景下operator(),这是一个法律背景.对参数的评估是未指定的,并且由于签名HELPER INVOKE_F_0...可能应该只包含普通的旧数据(用C++ 03用语),或者更具体地说[expr.call]/p7说:(通过@TC)

通过实现定义的语义有条件地支持传递具有非平凡复制构造函数,非平凡移动构造函数或非平凡析构函数的类类型(第9章)的潜在评估参数,其中没有相应的参数.

所以这段代码的问题是订单是未指定的,它依赖于表现良好的类型特定的编译器实现选择.

我们可以解决这个operator,问题如下:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  [](...){}((void(f(std::forward<Args>(args))), 0)...); 
}
Run Code Online (Sandbox Code Playgroud)

那么我们可以通过在初始化器中扩展来保证订单:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  int unused[] = {(void(f(std::forward<Args>(args))), 0)...}; 
  void(unused); // suppresses warnings
}
Run Code Online (Sandbox Code Playgroud)

但是当Args...空的时候上面的失败,所以添加另一个0:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  int unused[] = {0, (void(f(std::forward<Args>(args))), 0)...}; 
  void(unused); // suppresses warnings
}
Run Code Online (Sandbox Code Playgroud)

并没有很好的理由让编译器不能消除unused[]来自所有脑干,同时还评估fargs...秩序.

我的首选变体是:

template <class...F>
void do_in_order(F&&... f) { 
  int unused[] = {0, (void(std::forward<F>(f)()), 0)...}; 
  void(unused); // suppresses warnings
}
Run Code Online (Sandbox Code Playgroud)

它采用了nullary lambdas并且一次一个地运行它们,从左到右.(如果编译器可以证明顺序无关紧要,那么可以自由地运行它们).

然后我们可以用以下方法实现:

template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
  do_in_order( [&]{ f(std::forward<Args>(args)); }... );
}
Run Code Online (Sandbox Code Playgroud)

它将"奇怪的扩展"放在一个孤立的函数(do_in_order)中,我们可以在其他地方使用它.我们也可以编写do_in_any_order类似的工作方式,但要any_order明确:但是,除了极端的原因,在参数包扩展中以可预测的顺序运行代码可以减少意外并将头痛降到最低.

do_in_order技术的缺点是并非所有编译器都喜欢它 - 扩展包含包含整个子语句的语句的参数包并不是他们期望必须做的事情.


For*_*veR 13

实际上它以未指定的顺序f为每个参数调用函数args.

[](...){}
Run Code Online (Sandbox Code Playgroud)

创建lambda函数,它不执行任何操作并接收任意数量的参数(va args).

((f(std::forward<Args>(args)), 0)...)
Run Code Online (Sandbox Code Playgroud)

lambda的论点.

(f(std::forward<Args>(args)), 0)
Run Code Online (Sandbox Code Playgroud)

打电话f与转发的说法,送0给lambda.

如果您想要指定订单,可以使用以下内容:

using swallow = int[];
(void)swallow{0, (f(std::forward<Args>(args)), 0)...};
Run Code Online (Sandbox Code Playgroud)

  • @NoSenseEtAl因为它必须为你传入的每个参数取一个0.没有办法写"带有N args的lambda"所以这只是"lambda带来任何东西". (3认同)