应用于不同 lambda 的三元运算符会产生不一致的结果

Ternary operator applied to different lambdas produces inconsistent results

考虑下面使用三元运算符得到两个lambdas的公共函数指针类型

int main() {
  true ? [](auto) noexcept {} : [](int) {};
}

GCC-trunk 仅在 C++14 中接受它,但在 C++17/20 中拒绝它 (Demo):

<source>:2:8: error: operands to '?:' have different types 'main()::<lambda(auto:1)>' and 'main()::<lambda(int)>'
    2 |   true ? [](auto) noexcept {} : [](int) {};
      |   ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Clang-trunk 在所有 C++14/17/20 模式下接受它 (Demo)。

MSVC-trunk 仅在 C++20 中接受它,但在 C++14/17 中拒绝它 (Demo):

<source>(2): error C2446: ':': no conversion from 'main::<lambda_01e5bb79b5a210014fb78333f6af80f9>' to 'main::<lambda_57cf6f5767bc1bee4c1e1d9859a585d2>'
<source>(2): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

哪个编译器是正确的?

由于每个 lambda 表达式都有唯一的类型,并且两个 lambda 表达式都不能转换为另一个,因此 [expr.cond]/6 适用。

If the second and third operands do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands ([over.match.oper], [over.built]). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this subclause.

候选人是([over.built]/25):

For every type T, where T is a pointer, pointer-to-member, or scoped enumeration type, there exist candidate operator functions of the form

T       operator?:(bool, T, T);

我认为在这种情况下重载决议应该成功,因此,两个操作数都应该转换为 void(*)(int)

也就是说,IMO clang 是正确的。


编辑:我相信 [](auto) noexcept {} 可以转换为 void(*)(int),因为它有一个转换函数模板 template<class T> operator FP(),其中 FPvoid(*)(T)[expr.prim.lambda.closure]/9).

For a generic lambda with no lambda-capture, the closure type has a conversion function template to pointer to function. The conversion function template has the same invented template parameter list, and the pointer to function has the same parameter types, as the function call operator template. The return type of the pointer to function shall behave as if it were a decltype-specifier denoting the return type of the corresponding function call operator template specialization.

而且我相信模板参数推导应该使这个转换模板可用作 void(*)(int) ([temp.deduct.conv]/5) 的转换。

Template argument deduction is done by comparing the return type of the conversion function template (call it P) with the type specified by the conversion-type-id of the conversion-function-id being looked up (call it A) as described in [temp.deduct.type].

[...]

In general, the deduction process attempts to find template argument values that will make the deduced A identical to A. However, certain attributes of A may be ignored:

  • [...]
  • If the original A is a function pointer or pointer-to-member-function type, its noexcept.