是否通过未定义的“函数”左值调用“noexcept 函数”?

TSm*_*ith 8 c++ function-pointers undefined-behavior language-lawyer noexcept

[表达式调用]/6 :

通过函数类型与被调用函数定义的函数类型不同的表达式调用函数会导致未定义行为。

void f() noexcept {}; // function type is "noexcept function"
void (*pf)() = f; // variable type is "pointer to function"; initialized by result of [conv.fctptr]([conv.func](f))
int main()
{
    (*pf)(); // `*pf`: lvalue expression's function type is "function" (without noexcept!)
}
Run Code Online (Sandbox Code Playgroud)

根据引用的标准,上述调用是否会导致未定义的行为?

dfr*_*fri 4

C++14 的要求较弱,来自[expr.call]/6

\n
\n

[...] 通过函数类型具有语言链接的表达式调用函数而该表达式的函数类型具有与被调用函数定义的函数类型的语言链接不同的[...]

\n
\n

然而[expr.reinterpret.cast]/6包含类似但更强的要求:

\n
\n

函数指针可以显式转换为不同类型的函数指针。通过指向与类型不同的函数类型([dcl.fct])的指针调用函数的效果函数类型 ([dcl.fct]) 的指针来调用函数

\n
\n

P0012R1将异常规范作为类型系统的一部分,并针对 C++17 实现

\n
\n

函数的异常规范现在是 function\xe2\x80\x99s 类型的一部分:void f() noexcept(true);并且void f() noexcept(false);是两种不同类型的函数。函数指针可以在合理的方向上转换。(但是这两个函数f可能不会形成重载集。)此更改增强了类型系统,例如允许 API 需要非抛出回调。

\n
\n

并且还添加了[conv.fctptr]:

\n
\n

在第 4.11 节 [conv.mem] 之后添加新的部分:

\n
\n

4.12 [conv.fctptr]函数指针转换

\n

“指向 noexcept 函数的指针”类型的纯右值可以转换为\n“指向函数的指针”类型的纯右值。[...]

\n
\n
\n

但不包含对[expr.reinterpret.cast]/6的任何更改;可以说是无意的遗漏。

\n

CWG 2215强调了 [expr.call] 中与 [expr.reinterpret.cast]/6 相比的重复信息,将前者中较弱的要求标记为冗余。以下cplusplus / draft提交实施了 CWG 2215,并删除了较弱(冗余)的要求,将 [expr.reinterpret.cast]/6 变成非规范性注释,并将其(较强)规范性要求移至 [expr.call];最终,这一更强烈的要求被分成了一个单独的段落。

\n

这种混乱可以说导致了无意的(看似冲突的)规则:

\n
    \n
  • 指向函数\xe2\x80\ noexceptx9d 的类型为\xe2\x80\x9c 的纯右值可以转换为类型为\xe2\x80\x9c 的指向函数\xe2\x80\x9d 的指针的纯右值 ( [conv.fctptr]/1 ),和
  • \n
  • 通过函数类型仅因异常规范而不同的表达式调用函数是未定义的行为。
  • \n
\n

事实上,没有涉及此问题的缺陷报告,可以说应该提交一份新的报告。

\n