`for_each_arg` 的正确用法 - 转发过多?
Correct usage of `for_each_arg` - too much forwarding?
我很高兴发现 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;
}
我所有的questions/doubts都在上面代码示例中的注释中表示。
您代码中的每个 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
}
它接受零个 arg lambda,并从左到右运行它们。
然后将对 for_each_arg
的调用替换为:
do_in_order(
[&]{
result.add(std::forward<TArgs>(mArgs));
}...
);
缺点是更多的编译器不喜欢上面的内容。
do_in_order
中表达式的顺序由 n4296 中的 [dcl.init] 和 [dcl.init.list] 部分保证 8.5.4/4 8.5.4/1 8.5 /15 8.5/1。初始化是一个复制列表初始化(8.5/15 和 8.5.4/1),是一个“initializer-list of a braced-init-list" (8.5/1) 因此从左到右排序 (8.5.4/4)。
我很高兴发现 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;
}
我所有的questions/doubts都在上面代码示例中的注释中表示。
您代码中的每个 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
}
它接受零个 arg lambda,并从左到右运行它们。
然后将对 for_each_arg
的调用替换为:
do_in_order(
[&]{
result.add(std::forward<TArgs>(mArgs));
}...
);
缺点是更多的编译器不喜欢上面的内容。
do_in_order
中表达式的顺序由 n4296 中的 [dcl.init] 和 [dcl.init.list] 部分保证 8.5.4/4 8.5.4/1 8.5 /15 8.5/1。初始化是一个复制列表初始化(8.5/15 和 8.5.4/1),是一个“initializer-list of a braced-init-list" (8.5/1) 因此从左到右排序 (8.5.4/4)。