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

2021-12-13 00:00:00 templates c++ c++11 variadic-templates
template <class F, class... Args> 
void for_each_argument(F f, Args&&... args) { 
    [](...){}((f(std::forward<Args>(args)), 0)...); 
}

它最近在 isocpp.org 上没有解释.

It was recently featured on isocpp.org without explanation.

推荐答案

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

The short answer is "it does it not very well".

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

It invokes f on each of the args..., and discards the return value. But it does so in a way that leads to unexpected behavior in a number of cases, needlessly.

代码没有顺序保证,如果给定 Argf 返回值有一个重载的 operator,它可以有不幸的副作用.

The code has no ordering guarantees, and if the return value of f for a given Arg has an overloaded operator, it can have unfortunate side effects.

有一些空白:

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

我们将从内部开始.

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

f(std::forward<Args>(args)) is an incomplete statement that can be expanded with a .... It will invoke f on one of args when expanded. Call this statement INVOKE_F.

(INVOKE_F, 0)f(args) 的返回值,应用operator, 然后0>.如果返回值没有覆盖,则丢弃 f(args) 的返回值并返回 0.调用此 INVOKE_F_0.如果 f 返回一个带有覆盖 operator,(int) 的类型,这里就会发生不好的事情,如果该运算符返回一个非 POD 类型的类型,你可以得到有条件地支持"行为稍后.

(INVOKE_F, 0) takes the return value of f(args), applies operator, then 0. If the return value has no overrides, this discards the return value of f(args) and returns a 0. Call this INVOKE_F_0. If f returns a type with an overriden operator,(int), bad things happen here, and if that operator returns a non-POD-esque type, you can get "conditionally supported" behavior later on.

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

[](...){} creates a lambda that takes C-style variadics as its only argument. This isn't the same as C++11 parameter packs, or C++14 variadic lambdas. It is possibly illegal to pass non-POD-esque types to a ... function. Call this HELPER

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

HELPER(INVOKE_F_0...) is a parameter pack expansion. in the context of invoking HELPER's operator(), which is a legal context. The evaluation of arguments is unspecified, and due to the signature of HELPER INVOKE_F_0... probably should only contain plain old data (in C++03 parlance), or more specifically [expr.call]/p7 says: (via @T.C)

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

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.

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

So the problems of this code is that the order is unspecified and it relies on well behaved types or specific compiler implementation choices.

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

We can fix the operator, problem as follows:

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

然后我们可以通过在初始化器中扩展来保证顺序:

then we can guarantee order by expanding in an initializer:

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:

but the above fails when Args... is empty, so add another 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> 按顺序.

and there is no good reason for the compiler to NOT eliminate unused[] from existance, while still evaluated f on args... in order.

我的首选变体是:

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

它接受 nullary lambdas 并一次运行一个,从左到右.(如果编译器可以证明顺序无关紧要,则可以随意乱序运行它们).

which takes nullary lambdas and runs them one at a time, left to right. (If the compiler can prove that order does not matter, it is free to run them out of order however).

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

We can then implement the above with:

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

将奇怪的扩展"放在一个孤立的函数中(do_in_order),我们可以在其他地方使用它.我们也可以编写类似工作的 do_in_any_order,但使 any_order 清晰:然而,除非极端原因,在参数包扩展中以可预测的顺序运行代码可以减少意外和尽量减少头痛.

which puts the "strange expansion" in an isolated function (do_in_order), and we can use it elsewhere. We can also write do_in_any_order that works similarly, but makes the any_order clear: however, barring extreme reasons, having code run in a predictable order in a parameter pack expansion reduces surprise and keeps headaches to a minimum.

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

A downside to the do_in_order technique is that not all compilers like it -- expanding a parameter pack containing statement that contains entire sub-statements is not something they expect to have to do.

相关文章