正确使用“for_each_arg”-转发太多

Correct usage of `for_each_arg` - too much forwarding?

本文关键字:arg 转发 太多 each for      更新时间:2023-10-16

我真的很高兴发现了for_each_arg(...),这使得处理参数包变得更加容易。

template<class F, class...Ts>
F for_each_arg(F f, Ts&&...a) {
 return (void)std::initializer_list<int>{(ref(f)((Ts&&)a),0)...}, f;
}

但是,我对它的正确用法感到困惑。有许多参数需要完美转发,但我是否执行任何不必要的转发?

阅读代码会因过度的倾斜而变得更加困难。

struct UselessContainer
{
    // Expects a perfectly-forwarded item to emplace
    template<typename T> void add(T&&) { }   
};
// Creates an `UselessContainer` already filled with `mArgs...`
auto makeUselessContainer(TArgs&&... mArgs)
{
    using namespace std;
    UselessContainer result;
    for_each_arg
    (
        [&result, &mArgs...] // Am I capturing the `mArgs...` pack correctly here?
        (auto&& mX) // Am I passing the arguments to the lambda correctly here?
        { 
            // Is this `forward` necessary?
            result.add(forward<decltype(mX)>(mX)); 
            // Could it be replaced with
            // `result.add(forward(mX));` 
            // ?             
        }, 
        forward<TArgs>(mArgs)... // I assume this `forward` is necessary.
    );
    return result;
}

我所有的问题/疑问都在上面代码示例中的注释中表达。

代码中的每个forward确实都是必要的,以便将所有参数完美地转发到最后。右值引用的名称是左值,因此除非您每次传递参数时都转发,否则值类别信息将丢失。
此外,如果没有明确的模板参数列表,就不可能调用forward,因为模板参数仅用于一个非推导上下文。事实上,在没有显式参数列表的情况下调用的函数模板无法完成这项工作。

您可以尝试使用宏来稍微缩短代码:

#define FORWARD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)

然后它变成

for_each_arg
(
    // Removed superfluous capture
    [&result] (auto&& mX) { 
        result.add(FORWARD(mX));       
    }, 
    FORWARD(mArgs)...
);

也可以首先使用宏而不是for_each_arg

#define FOR_EACH_ARG(...) (void)std::initializer_list<int>{((__VA_ARGS__),0)...}
FOR_EACH_ARG( result.add(forward<TArgs>(mArgs)) );
for_each_arg (
  [&](auto&& mX){
    result.add(std::forward<decltype(mX)>(mX));
  },
  std::forward<TArgs>(mArgs)...
);

只需在制作这种 lambda 时捕获&即可。 如果必须列出,则只需要捕获&result

forward<?>始终与类型参数一起使用。

注意 Eric 的for_each_arg并不完美,主要是用 140 个字符或更少的字符来做。 ;) 它的瑕疵是温和的,在这里是无害的。

这是一个替代方案:

首先,写这个:

template<class...Fs>
void do_in_order(Fs&&...fs){
  int _[]={0,
    (((void)(std::forward<Fs>(fs)())),0)...
  };
  (void)_; // kills warnings
}

它需要零参数 lambda,并从左到右运行它们。

然后将对for_each_arg的调用替换为:

do_in_order(
  [&]{
    result.add(std::forward<TArgs>(mArgs));
  }...
);

缺点是更多的编译器不会喜欢上述内容。

do_in_order中表达式的顺序由 n4296 8.5.4/4 8.5.4/1 8.5/15 8.5/15 8.5/1 中的 [dcl.init] 和 [dcl.init.list] 部分保证。 初始化是复制列表初始化(8.5/15 和 8.5.4/1),是"大括号初始化列表的初始值设定项列表"(8.5/1),因此从左到右排序(8.5.4/4)。