Home > Mobile >  Ternary operator applied to different lambdas produces inconsistent results
Ternary operator applied to different lambdas produces inconsistent results

Time:04-11

Consider the following which uses the ternary operator to get the common function pointer type of the two lambdas

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

GCC-trunk only accepts it in C 14 but rejects it in C 17/20 with (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 accepts it in all C 14/17/20 mode (Demo).

MSVC-trunk only accepts it in C 20 but rejects it in C 14/17 with (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

Which compiler is right?

CodePudding user response:

Since each lambda expression has a unique type, and neither lambda expression is convertible to the other, [expr.cond]/6 applies.

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.

The candidates are ([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);

I believe that overload resolution should succeed in this case, and as a result, both operands should be converted to void(*)(int).

That is, IMO clang is correct.


Edit: I believe that [](auto) noexcept {} is convertible to void(*)(int), because it has a conversion function template template<class T> operator FP(), where FP is void(*)(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.

And I believe that template argument deduction should make this conversion template usable as a conversion to 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.

CodePudding user response:

Both, say the same thing, you can't use ternary operator on different types. Lambda functions must have identical format

  • Related