据我所知,当包含参数包的模式右侧出现省略号(...)时,模式会对包中的每个参数展开一次。然而,尽管我已经能够通过扩展找到模式的孤立示例,但我无法找到模式构成的定义。从我所看到的,空格在模式的定义中没有任何作用,但是括号有。例如,在此示例中:
template<typename ... Ts>
void func(Ts)
{
do_something(validate(Ts)...);
}
do_something
行将扩展为:
do_something(validate(var1), validate(var2), validate(var3))
如果Ts
碰巧代表三个变量。相比之下:
do_something(validate(Ts...));
将扩展为:
do_something(validate(var1, var2, var3));
因此,明确括号与确定模式的开始和结束位置有关。我也可以看到空白没有。但这只能让我到目前为止。我想确切地知道什么构成了一个模式,以及它将如何扩展。我尝试搜索C ++标准,但发现太多的&#34;参数包&#34;使这有效。有人可以给我一个&#34;模式&#34;的定义,或定义的链接,或两者兼而有之?
更新:为了限制我的问题范围,我想关注函数调用中出现模式的情况。我已经相应地编辑了标题。对不起,我从一开始就没有说清楚。
答案 0 :(得分:7)
模式在[temp.variadic]/4下的标准中定义:(通过@t.c.)
pack扩展由模式和省略号组成,其实例化在列表中生成零个或多个模式的实例化(如下所述)。模式的形式取决于扩展发生的环境。包扩展可以在以下上下文中发生:
- 在函数参数包([dcl.fct])中;模式是没有省略号的parameter-declaration。
- 在作为包扩展的模板参数包([temp.param])中:
- 如果模板参数包是parameter-declaration;模式是没有省略号的parameter-declaration;
- 如果模板参数包是type-parameter且template-parameter-list;模式是没有省略号的相应type-parameter。
- 在initializer-list([dcl.init]);模式是initializer-clause。
- 在base-specifier-list中(条款[class.derived];模式为base-specifier。
- 在mem-initializer-list([class.base.init])中mem-initializer mem-initializer-id表示基类;模式是mem-initializer。
- 在template-argument-list([temp.arg]);模式是template-argument。
- 在dynamic-exception-specification([except.spec]);模式是type-id。
- 在attribute-list([dcl.attr.grammar]);模式是attribute。
- 在alignment-specifier([dcl.align]);模式是没有省略号的alignment-specifier。
- 在capture-list([expr.prim.lambda]);模式是capture。
- 在
sizeof...
表达式中[(expr.sizeof]);模式是identifier。- 在fold-expression([expr.prim.fold]);模式是包含未扩展参数包的cast-expression。
标准草案中的上述引言说明链接中描述的部分语法是&#34;模式&#34;正在扩大。要理解它,您需要知道如何描述C ++语法以及有关如何在标准文本本身中使用它的例外情况;但是,如果你有基本的BNF知识和一点耐心,你可以解决它。这些名字通常也很有用。
但是,您可以使用...
并且大多数情况下都不会理解它。
通用规则很简单:你有一些C ++语法(称为模式)以通常的方式解析,其中一组类型被视为一个类似单一类型,一包文字被视为单个文字。然后,在它结束时,你有一个 ... 扩展器。然后它会立即将所有未扩展的包装在C ++语法中( pattern )并展开它。
这在每个上下文中的工作原理是不同;它不仅仅是一次宏观扩张。上面列举了...
有效的上下文;扩展的影响在有效的每个点都列在标准中。
在...
的最平凡的用例中,模式是一个表达式,如果每个副本由,
(不是operator,
分隔,则表达式会被展开,但是另一个&#34; normal&#34; one),通常在预期事物列表的上下文中(函数调用,初始化列表等)。
有函数参数声明上下文(其中...
都扩展了函数参数的类型,并引入了一个参数名称的新包),在模板参数列表中(通常引入包),等等
sizeof...
有点奇怪:对于sizeof...
,它计算()
中传递的包中有多少元素。这样做的方式不同,因为...
不适用于左边的&#34;结构&#34;。
对于alignas(X...)
,我们最终得到alignas(X@0), alignas(X@1), ...
(其中@0
是我的第一个元素的伪代码),因为alignas(X@0, X@1, ...)
无效C ++(再次@TC在下面的评论中。)
对于继承,它创建一组基类。对于mem-initializer-lists,它允许您将ctor参数传递给所述基类包。对于lambda,它会为您提供有限的包捕获(在我检查的扩展时,表达捕获并不完整)。
模式是扩展的东西。重要的是,扩展模式不会被另一个模式扩展器扩展:因此std::array< Ts, sizeof...(Ts) >...
是一组各种类型的数组,每个数组都有一些元素,这些元素由包本身的大小决定。 sizeof...(Ts)
&#34;算作扩展&#34;其Ts
中的()
,即使...
不是&#34;其权利&#34;,因为该语言定义了Ts
{1}}作为由()
扩展的模式。
一般情况下的模式不能称为表达式,因为类型不是表达式,而某些模式是表达式(或至少扩展为表达式列表)。在某些情况下,...
将类型模式扩展为扩展的类型包(如throw表达式中)。
一般规则,你将...
视为在本地环境中以适当的方式扩展左边的东西,适用于除...
之外的几乎所有东西(这是一个神奇的操作符告诉你参数包中有多少个元素)。只是在角落的情况下才会产生一个不错的模型。根据我的经验,更糟糕的是它会导致代码在您认为应该编译时无法编译;在这种情况下,您可以学习解决方法。就像,记住一个简单的声明&#34;没有本地环境&#34;,所以你不能sizeof...
,而是解决方法a = std::get<Is>(tup))...;
(可能必须键入dede (void)(int[]){0,(a = std::get<Is>(tup)),0)...};
)我们为包扩展提供了一个上下文(创建一个数组)。
C ++ 1z的折叠表达式是另一个奇怪的地方,int[]
不适用于左边;在这里,他们希望扩展一个二元运算符,所以&#34;在左边&#34;并不像通常的&#34;一元&#34;那样有意义。扩展
答案 1 :(得分:0)
模式由与...
扩展语法的关联定义。所以validate(ts)
仅仅是无意义文本的片段。但是validate(ts)...
使validate(ts)
部分成为扩展的模式。因此,...
如何影响相关代码。
通常,解包语法指定...
左侧的内容是模式。使用C ++ 17的折叠表达式,这变得有点复杂。但一般来说,如果你想知道模式是什么,找到...
,然后立即查看左边的表达式。
模式中可能存在的具体限制完全取决于扩展的位置以及包含的类型(类型与非类型等)。该模式必须是任何合法代码,无论它在何种环境中被扩展。