【问题标题】:Iterating through parameters of a variadic function template using variadic lambda使用可变参数 lambda 遍历可变参数函数模板的参数
【发布时间】:2014-06-18 15:09:03
【问题描述】:

假设我们有以下函数模板:

template <typename Functor, typename... Arguments>
void IterateThrough(Functor functor, Arguments&&... arguments)
{
    // apply functor to all arguments
}

该功能通常实现如下:

template <typename Functor, typename... Arguments>
void IterateThrough1(Functor functor, Arguments&&... arguments)
{
    int iterate[]{0, (functor(std::forward<Arguments>(arguments)), void(), 0)...};
    static_cast<void>(iterate);
}

另一种方式:

struct Iterate
{
    template <typename... Arguments>
    Iterate(Arguments&&... arguments)
    {
    }
};

template <typename Functor, typename... Arguments>
void IterateThrough2(Functor functor, Arguments&&... arguments)
{
    Iterate{(functor(std::forward<Arguments>(arguments)), void(), 0)...};
}

我发现了另一种使用可变参数 lambda 的方法:

template <typename Functor, typename... Arguments>
void IterateThrough3(Functor functor, Arguments&&... arguments)
{
    [](...){}((functor(std::forward<Arguments>(arguments)), void(), 0)...);
}

与前两种方法相比,这种方法有什么优缺点?

【问题讨论】:

  • 您的结构构造函数可以使用省略号而不是参数包。
  • @dyp 你的意思是一个简单的可变参数构造函数吗?但这不会违反您在下面引用的规则吗?
  • 我的意思是Iterate(...) {}。对花括号初始化列表的元素的评估是正确排序的,因此 Iterate{((void)functor(arguments), 0)...}; 的“函数参数评估顺序”参数被否决
  • @dyp 我现在明白了,谢谢。由于operator void() 在类decltype(functor(argument)) 中可能存在(void)functor(arguments), 0 的形式不危险吗?
  • [class.conv.fct]/1 "转换函数永远不会用于将(可能是 cv 限定的)对象转换为 [...] 或(可能是 cv 限定的)void 。”所以你可以声明一个,但它不会被使用。我的结论是它不会造成任何麻烦。

标签: c++ templates c++11 lambda variadic-functions


【解决方案1】:

functor 的调用现在未排序。编译器可以按照它想要的任何顺序使用扩展参数调用functor。例如,IterateThrough3(functor, 1, 2) 可以是 functor(1); functor(2);,也可以是 functor(2); functor(1);,而其他两个总是 functor(1); functor(2);

该标准的第 8.5.4/4 节要求 {} 初始化程序中的任何表达式从左到右进行计算。

在花括号初始化列表的初始化列表中,初始化子句,包括任何由 pack 产生的子句 扩展 (14.5.3),按照它们出现的顺序进行评估。

第 5.2.2/4 节规定,函数调用的参数可以按任何顺序计算。

当一个函数被调用时,每个参数(8.3.5)都应该被初始化(8.5,12.8,12.1),并使用其对应的 争论。 [注意:此类初始化彼此之间的顺序不确定(1.9)- 尾注]

这可能不包括评估顺序的措辞(我找不到 ATM),但众所周知,函数的参数是以未指定的顺序评估的。编辑:有关标准报价,请参阅@dyp 的评论。

【讨论】:

  • 您能否提供标准的报价以确认您的话?
  • @Constructor 非规范性注释是否足够? 1.9/15 "[注意: 与不同参数表达式相关的值计算和副作用是无序的。— 结束注释]"
  • @Constructor 查看更新的评论。据我所知,规范文本并没有专门解决函数参数。这些可能属于更一般的不同子表达式排序的类别。
  • @dyp 感谢您的参考和解释。
  • @Simple “这可能不包括评估顺序的措辞” 可能包含在 1.9/15 中,“除非另有说明,对单个运算符的操作数和单个表达式的子表达式是无序的。”规范性文字,但非常不具体
【解决方案2】:

当您使用可变参数 lambda 时,根据语言规范未指定参数的评估顺序(这反过来意味着 functor(argument) 的评估可能是程序员不知道的任何顺序)。这是唯一的区别。你可以在这个网站上搜索“参数评估的顺序”,你会看到很多主题。

至于可变参数模板化构造方法,只要你使用列表初始化来调用它就应该可以工作,否则它会和 lambda 有同样的问题。请注意,GCC(最高 4.8.2)有错误,所以这不起作用,虽然我不知道它是否被最新版本的 GCC 修复。

【讨论】:

  • 这条规则是否适用于任何可变参数函数?
  • @Constructor:适用于任何函数。未指定函数参数的评估顺序。
  • 哦,我现在可以说我曾经听说过这件事。 :-) 谢谢。
  • 是的,我知道参数参数倒序传递时这个奇怪的错误。
【解决方案3】:

如果您的目标是没有外部机器的单线,您可以使用接受 std::initializer_list 的 lambda:

template <typename Functor, typename... Arguments>
void IterateThrough3(Functor functor, Arguments&&... arguments)
{
    [](std::initializer_list<int>){}(
        {((void)functor(std::forward<Arguments>(arguments)), 0)...}
    );
}

【讨论】:

  • 我猜可以写成(void)std::initializer_list&lt;int&gt;{((void)functor(arguments), 0)...};
  • @dyp 转换为void 不会关闭所有编译器上的“未使用变量”警告,通过未命名参数传递给函数更便携。
  • 谢谢。我注意到我完全忘记了std::forward。没有人纠正我。 :-)
  • 我认为有几种可能的方法来做这样的事情。例如:[](const int(&amp;)[sizeof...(Arguments)]){}({((void)functor(std::forward&lt;Arguments&gt;(arguments)), 0)...});[](std::array&lt;int, sizeof...(Arguments)&gt;){}({((void)functor(std::forward&lt;Arguments&gt;(arguments)), 0)...})。但正如@dyp 指出的那样,这里并不那么有趣。
  • @Casey 我没有构造一个变量,只是一个临时的。所以唯一的警告应该是“未使用的表达式结果”之类的东西,我想应该通过转换为 void 来消除它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-01
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-05
  • 1970-01-01
相关资源
最近更新 更多