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

What does this variadic template code do?

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

它最近在 isocpp.org 上被推荐,但没有任何解释。

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

[](...){}

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

((f(std::forward<Args>(args)), 0)...)

lambda 的参数。

(f(std::forward<Args>(args)), 0)

使用转发的参数调用 f,将 0 发送到 lambda。

如果你想要指定顺序你可以使用following thing:

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

简短的回答是 "it does it not very well"。

它在每个 args... 上调用 f,并丢弃 return 值。但它这样做的方式在许多情况下会导致不必要的意外行为。

代码没有顺序保证,如果给定 Argf 的 return 值重载了 operator,,它可能会产生不幸的副作用。

有点白space:

[](...){}(
  (
    f(std::forward<Args>(args)), 0
  )...
);

我们将从内部开始。

f(std::forward<Args>(args)) 是一个不完整的语句,可以用 ... 扩展。展开时,它将在 args 之一上调用 f。调用此语句 INVOKE_F.

(INVOKE_F, 0) 采用 f(args) 的 return 值,应用 operator, 然后 0。如果 return 值没有覆盖,这会丢弃 f(args) 和 return 的 return 值 0。称之为 INVOKE_F_0。如果 f return 是一个覆盖了 operator,(int) 的类型,这里会发生不好的事情,如果那个运算符 return 是一个非 POD 类型,你可以得到 "conditionally supported" 稍后的行为。

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

HELPER(INVOKE_F_0...)是参数包扩展。在调用 HELPERoperator() 的上下文中,这是一个合法的上下文。参数的评估是未指定的,并且由于 HELPER INVOKE_F_0... 的签名可能应该只包含普通的旧数据(用 C++03 的说法),或者更具体地说 [expr.call]/ p7 说:(通过@T.C)

Passing a potentially-evaluated argument of class type (Clause 9) having a nontrivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.

所以这段代码的问题是顺序未指定它依赖于行为良好的类型特定的编译器实现选择。

我们可以按如下方式修复 operator, 问题:

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

然后我们可以通过在初始值设定项中扩展来保证顺序:

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
}

但是当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
}

并且编译器没有充分的理由不从存在中消除 unused[],同时仍然按顺序在 args... 上评估 f

我的首选变体是:

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

它接受 nullary lambda 并 运行 从左到右一次一个。 (如果编译器可以证明顺序无关紧要,那么可以随意 运行 它们的顺序)。

然后我们可以通过以下方式实现上述内容:

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

将 "strange expansion" 放在一个独立的函数中 (do_in_order),我们可以在其他地方使用它。我们也可以编写类似工作的 do_in_any_order,但使 any_order 清晰:但是,除非极端原因,在参数包扩展中以可预测的顺序使用代码 运行 可以减少意外并保持头痛最低限度。

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