是否可以将无作用域枚举传递给 va_start?

Luk*_*rth 5 c++ enums variadic-functions language-lawyer c++17

问题

\n

我有一个遗留代码库,其中包含以下内容:

\n
enum 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 会发出以下警告:

\n
<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

1. 无作用域枚举(在函数调用中)始终提升为其基础类型。

\n

整数提升规则[conv.prom]说:

\n
\n
    \n
  1. 基础类型不固定 (7.2) 的无作用域枚举类型的纯右值可以转换为以下第一个类型的纯右值,该类型可以表示枚举的所有值: [\xe2\x80\xa6]

    \n
  2. \n
  3. 基础类型固定 (7.2) 的无作用域枚举类型的纯右值可以转换为其基础类型的纯右值。[\xe2\x80\xa6]

    \n
  4. \n
\n
\n

虽然这些规则仅规定可以进行整数转换,[expr.call]/7但表示:

\n
\n

如果参数具有受整型提升 [\xe2\x80\xa6] 约束的整型或枚举类型,则参数的值会在调用之前转换为提升的类型。

\n
\n

所以基本上,任何enum(不是enum class)必须在调用之前提升为其基础类型。

\n

2.va_start必须采用与结果类型“兼容”的参数

\n

的规则va_start[cstdarg.syn]/1

\n
\n

如果参数 parmN 的类型 [\xe2\x80\xa6] 与传递没有参数的参数时产生的类型不兼容,则行为未定义。

\n
\n

这里我不确定这是什么意思type that results when passing an argument for which there is no parameterparmN我猜想,当您指定至少一个可变参数时,它就是参数的(升级!)类型。不确定为什么会影响类型parmN,但基本上我猜这意味着像这样的调用

\n
someFunc(MyEnum::Foo, 42);\n
Run Code Online (Sandbox Code Playgroud)\n

在这种情况下,42argument for which there is no parameter.

\n

无论如何,传递给的参数的类型va_startMyEnum,并且参数的(提升)类型是某种整数类型。引出第三点:

\n

3. 目前尚不清楚“兼容”在这种情况下意味着什么

\n

虽然 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
\n

因此,没有给出什么是“兼容类型”的规范。所以对我来说,不清楚如何[support.runtime]/3解释其中的“兼容”要求。如果我们应用旧的 C 标准兼容性规则,我猜enums 只能与enums 兼容,而不是与ints 兼容。

\n

如果这是正确的解释,我认为没有办法enumva_start.

\n

问题

\n

在 C++17 中,是否有一种将enum函数的​​无作用域参数传递给 的非未定义行为方式va_start?或者,换句话说:给定一个可变参数函数,该函数具有无范围enum参数作为其最后一个命名参数(在 之前...),是否有一种方法可以检索va_list

\n

eca*_*mur 1

这里我不确定“传递没有参数的参数时产生的类型”是什么意思。

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