这个可变参数模板代码做什么? [英] What does this variadic template code do?

查看:111
本文介绍了这个可变参数模板代码做什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

它最近在isocpp.org上推荐,无需解释。

解决方案

简单的答案是它不太好。



args ... 中的每一个上的 f ,并丢弃返回值。但它在某些情况下会导致意想不到的行为,在不必要的情况下。



代码没有排序保证,如果返回值<$对于给定的 Arg 具有重载的运算符,它可以具有 f 不幸的副作用。



有一些空格:

  (...){}(

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

我们将从里面开始。



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



(INVOKE_F,0)使用运算符,,然后 0 的返回值。如果返回值没有覆盖,则舍弃 f(args)的返回值,并返回 0 。调用 INVOKE_F_0 。如果 f 返回具有覆盖运算符的类型,(int),则会发生错误,非POD类型,您可以稍后获得条件支持行为。



[](...){} / code>创建一个lambda,它使用C风格变量作为其唯一的参数。这与C ++ 11参数包或C ++ 14 variardic lambdas不同。将非POD类型类型传递到 ... 函数可能是非法的。调用此 HELPER



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


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


因此,此代码的问题是顺序未指定它依赖于良好的行为类型<

我们可以修复运算符,问题,如下所示:

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

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

  template< class F,class ... Args> 
void for_each_argument(F f,Args& ... args){
int unused [] = {(void(f(std :: forward& ...};
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& 0)...}
void(unused); //禁止警告
}

,没有理由不让编译器 unused [] from existsance,同时仍然评估 f on args ...

  

> template< class ... F>
void do_in_order(F& ... f){
int unused [] = {0,(void(std :: forward F(f) 。};
void(unused); //禁止警告
}

它使用nullary lambdas并一次运行一个,左到右。 (如果编译器可以证明顺序没有关系,可以自由地运行顺序)



然后我们可以用下面的代码实现上面的代码:

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

将奇怪扩展 c> do_in_order ),我们可以在其他地方使用它。我们也可以写 do_in_any_order ,它的工作原理类似,但使 any_order 清除:然而,除非极端的原因,



do_in_order 技术是不是所有的编译器都喜欢它 - 扩展包含语句,包含整个子语句的参数包不是他们期望做的。


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

It was recently featured on isocpp.org without explanation.

解决方案

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

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.

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.

With some white space:

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

We will start from the inside.

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) 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.

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

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)

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.

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
}

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
}

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

My preferred variant is:

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

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)); }... );
}

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.

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.

这篇关于这个可变参数模板代码做什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆