問(wèn)題描述
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((f(std::forward<Args>(args)), 0)...);
}
它最近在 isocpp.org 上沒(méi)有解釋.
It was recently featured on isocpp.org without explanation.
推薦答案
簡(jiǎn)短的回答是它做得不是很好".
The short answer is "it does it not very well".
它對(duì)每個(gè) args...
調(diào)用 f
,并丟棄返回值.但這樣做的方式在許多情況下會(huì)導(dǎo)致意外行為,這是不必要的.
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.
代碼沒(méi)有順序保證,如果給定 Arg
的 f
返回值有一個(gè)重載的 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
)...
);
我們將從內(nèi)部開始.
f(std::forward(args))
是一個(gè)不完整的語(yǔ)句,可以用 ...
展開.展開時(shí),它將在 args
之一上調(diào)用 f
.調(diào)用此語(yǔ)句 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)
的返回值,應(yīng)用operator,
然后0
>.如果返回值沒(méi)有覆蓋,則丟棄 f(args)
的返回值并返回 0
.調(diào)用此 INVOKE_F_0
.如果 f
返回一個(gè)帶有覆蓋 operator,(int)
的類型,這里就會(huì)發(fā)生不好的事情,如果該運(yùn)算符返回一個(gè)非 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.
[](...){}
創(chuàng)建一個(gè)將 C 風(fēng)格的可變參數(shù)作為其唯一參數(shù)的 lambda.這與 C++11 參數(shù)包或 C++14 可變參數(shù) lambda 不同.將非 POD 類型的類型傳遞給 ...
函數(shù)可能是非法的.調(diào)用這個(gè)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...)
是一個(gè)參數(shù)包擴(kuò)展.在調(diào)用 HELPER
的 operator()
的上下文中,這是一個(gè)合法的上下文.參數(shù)的評(píng)估未指定,并且由于 HELPER
INVOKE_F_0...
的簽名可能應(yīng)該只包含普通的舊數(shù)據(jù)(在 C++03 中),或者更多特別是 [expr.call]/p7 說(shuō):(通過(guò)@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)
傳遞具有非平凡復(fù)制構(gòu)造函數(shù)、非平凡移動(dòng)構(gòu)造函數(shù)或非平凡析構(gòu)函數(shù)的類類型(第 9 條)的潛在評(píng)估參數(shù),沒(méi)有相應(yīng)的參數(shù),有條件地支持實(shí)現(xiàn)定義的語(yǔ)義.
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.
所以這段代碼的問(wèn)題是順序未指定并且它依賴于行為良好的類型或特定的編譯器實(shí)現(xiàn)選擇.
So the problems of this code is that the order is unspecified and it relies on well behaved types or specific compiler implementation choices.
我們可以修復(fù)operator,
問(wèn)題如下:
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)...);
}
然后我們可以通過(guò)在初始化器中擴(kuò)展來(lái)保證順序:
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
}
但是當(dāng) Args...
為空時(shí),上面會(huì)失敗,所以添加另一個(gè) 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
}
并且編譯器沒(méi)有充分的理由不從存在中消除 unused[]
,同時(shí)仍然在 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 并一次運(yùn)行一個(gè),從左到右.(如果編譯器可以證明順序無(wú)關(guān)緊要,則可以隨意亂序運(yùn)行它們).
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).
然后我們可以通過(guò)以下方式實(shí)現(xiàn)上述內(nèi)容:
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)); }... );
}
將奇怪的擴(kuò)展"放在一個(gè)孤立的函數(shù)中(do_in_order
),我們可以在其他地方使用它.我們也可以編寫類似工作的 do_in_any_order
,但使 any_order
清晰:然而,除非極端原因,在參數(shù)包擴(kuò)展中以可預(yù)測(cè)的順序運(yùn)行代碼可以減少意外和盡量減少頭痛.
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
技術(shù)的一個(gè)缺點(diǎn)是并非所有編譯器都喜歡它——擴(kuò)展包含整個(gè)子語(yǔ)句的語(yǔ)句的參數(shù)包并不是他們期望必須做的事情.
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.
這篇關(guān)于這個(gè)可變參數(shù)模板代碼有什么作用?的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!