通过 "function" 左值调用 "noexcept function" 是否未定义?

Is calling a "noexcept function" through a "function" lvalue undefined?

[expr.call]/6:

Calling a function through an expression whose function type is different from the function type of the called function's definition results in undefined behavior.

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!)
}

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

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

[...] Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function's definition is undefined ([dcl.link]). [...]

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

A function pointer can be explicitly converted to a function pointer of a different type. The effect of calling a function through a pointer to a function type ([dcl.fct]) that is not the same as the type used in the definition of the function is undefined.

P0012R1 made exception specifications to be part of the type system, and was implemented for C++17

The exception specification of a function is now part of the function’s type: void f() noexcept(true); and void f() noexcept(false); are functions of two distinct types. Function pointers are convertible in the sensible direction. (But the two functions f may not form an overload set.) This change strengthens the type system, e.g. by allowing APIs to require non-throwing callbacks.

并且添加了[conv.fctptr]:

Add a new section after section 4.11 [conv.mem]:

4.12 [conv.fctptr] Function pointer conversions

A prvalue of type "pointer to noexcept function" can be converted to a prvalue of type "pointer to function". [...]

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

CWG 2215 highlighted the duplicated information in [expr.call] compared to [expr.reinterpret.cast]/6, flagging the weaker requirement in the former as redundant. The following cplusplus / draft commit 实施了 CWG 2215,并删除了较弱(冗余)的要求,使 [expr.reinterpret.cast]/6 成为非规范性注释并将其(更强)规范性要求移至 [expr.call];最终这个更强的要求被分解成它自己的段落。

这种混淆可以说导致了无意的(看似矛盾的)规则:

  • “指向 noexcept 函数的指针”类型的纯右值可以转换为“指向函数的指针”([conv.fctptr]/1) 类型的纯右值,并且
  • 通过函数类型仅在其异常规范上不同的表达式调用函数是未定义的行为。

Afaic,没有关于此问题的缺陷报告,应该提交一份新报告。