Luk*_*rth 5 c++ enums variadic-functions language-lawyer c++17
我有一个遗留代码库,其中包含以下内容:
\nenum MyEnum { Foo, Bar, Baz };\n\nvoid someFunc(MyEnum enumVal, ...)\n{\n va_list args;\n va_start(args, enumVal); \n // do something with args...\n}\n
Run Code Online (Sandbox Code Playgroud)\n我无法轻松更改MyEnum
为作用域枚举,因为它在我们的整个代码库中使用(其中一些我什至无法访问)。编译此代码(使用 \'-Wall -std=c++17\'),Clang 14 会发出以下警告:
<source>:8:20: warning: passing an object that undergoes default argument promotion to \'va_start\' has undefined behavior [-Wvarargs]\n va_start(args, enumVal); \n
Run Code Online (Sandbox Code Playgroud)\n据我所知,这里的问题是 C++(17,但对于 11 或 14 应该是相同的)标准中这三个规则的组合(或缺少这三个规则,请参见第三点):
\n整数提升规则[conv.prom]
说:
\n\n\n
\n- \n
基础类型不固定 (7.2) 的无作用域枚举类型的纯右值可以转换为以下第一个类型的纯右值,该类型可以表示枚举的所有值: [\xe2\x80\xa6]
\n- \n
基础类型固定 (7.2) 的无作用域枚举类型的纯右值可以转换为其基础类型的纯右值。[\xe2\x80\xa6]
\n
虽然这些规则仅规定可以进行整数转换,[expr.call]/7
但表示:
\n\n如果参数具有受整型提升 [\xe2\x80\xa6] 约束的整型或枚举类型,则参数的值会在调用之前转换为提升的类型。
\n
所以基本上,任何enum
(不是enum class
)必须在调用之前提升为其基础类型。
va_start
必须采用与结果类型“兼容”的参数的规则va_start
为[cstdarg.syn]/1
:
\n\n如果参数 parmN 的类型 [\xe2\x80\xa6] 与传递没有参数的参数时产生的类型不兼容,则行为未定义。
\n
这里我不确定这是什么意思type that results when passing an argument for which there is no parameter
。parmN
我猜想,当您指定至少一个可变参数时,它就是参数的(升级!)类型。不确定为什么会影响类型parmN
,但基本上我猜这意味着像这样的调用
someFunc(MyEnum::Foo, 42);\n
Run Code Online (Sandbox Code Playgroud)\n在这种情况下,42
是argument for which there is no parameter
.
无论如何,传递给的参数的类型va_start
是MyEnum
,并且参数的(提升)类型是某种整数类型。引出第三点:
虽然 C 标准明确说明了哪些类型是“兼容的”(请参阅6.2.7 Compatible type and composite type
C 标准),但 C++17 标准在其与 C 的差异列表中明确说明,[diff.basic]/6.9
:
\n\n更改:C 在多个地方允许 \xe2\x80\x9c 兼容类型\xe2\x80\x9d,而 C++ 则不允许。
\n
因此,没有给出什么是“兼容类型”的规范。所以对我来说,不清楚如何[support.runtime]/3
解释其中的“兼容”要求。如果我们应用旧的 C 标准兼容性规则,我猜enum
s 只能与enum
s 兼容,而不是与int
s 兼容。
如果这是正确的解释,我认为没有办法enum
在va_start
.
在 C++17 中,是否有一种将enum
函数的无作用域参数传递给 的非未定义行为方式va_start
?或者,换句话说:给定一个可变参数函数,该函数具有无范围enum
参数作为其最后一个命名参数(在 之前...
),是否有一种方法可以检索va_list
?
这里我不确定“传递没有参数的参数时产生的类型”是什么意思。
enumVal
它意味着将在省略号中传递的类型。这是在http://eel.is/c++draft/expr.call#11.sentence-1定义的:
当给定参数没有参数时,参数的传递方式使得接收函数可以通过调用 来获取参数的值
va_arg
。
换句话说,您可以编写va_start(args, arg)
ifarg
的类型T
,这样,如果arg
以省略号传递,va_arg(args, T)
则将是有效的。
给定一个可变参数函数,该函数具有无作用域枚举参数作为其最后一个命名参数(在...之前),有没有办法检索 va_list?
不。也许您可以将有问题的函数移至 C 源文件。否则,您可以等待 C++ 采用 C23 unary va_start
。请参阅https://en.cppreference.com/w/c/variadic/va_start