Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSVC error when using capture-less lambda expressions as second and third operand of conditional operator

The code below is happily accepted by both GCC and Clang with -std=c++14 but causes a compile error with Visual Studio 2013.

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int main() {
    auto increasing = [](int lhs, int rhs){return lhs < rhs;};
    auto decreasing = [](int lhs, int rhs){return lhs > rhs;};
    std::vector<int> v(0, 10);
    bool increase = true;
    std::sort(v.begin(), v.end(), increase ? increasing : decreasing);
    return 0;
}

The error is:

main.cpp(11): error C2446: ':': no conversion from 'main::<lambda_0228ee097b83254cfd8aa5f4015a245b>' to 'main::<lambda_cb3b816d067baa9d4462132ee332363c>' main.cpp(11): note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

I guess my question is which compiler is compliant here, I am guessing that it is not MSVC, and is there a part of the standard which explicitly deals with this situation?

like image 685
sjdowling Avatar asked Jan 16 '15 17:01

sjdowling


1 Answers

Since neither lambda capture they can be converted to function pointers with compatible signatures, so gcc and clang are correct here.

There is a gcc bug report which summarizes this topic well: [c++ lambda] error in assigning lambda expr though "operator?:" while capturing that covers this and says:

The compiler behaviour looks correct to me. The difference of the lambda expressions in bar and foo3 compared to the other two is that these are capture-free lambdas and thus have a conversion function to function pointer.

Each lambda expression corresponds to a unique class type, so what we have in foo1 and foo2 can be compared with the following class-example:

struct A{}; struct B{};
void f() { false ? A() : B(); }

This expression has no common type for the conditional operator and is ill-formed.

What we have in bar and foo3 can be compared with the following class-example :

struct A
{
    typedef void (*F)();
    operator F();
};

struct B
{
    typedef void (*F)();
    operator F();
};

void f() { false ? A() : B(); }

This is well-formed, because in the last step of the conditional operator conversion attempts (5.16p5), more general conversions are attempted and these find the common pointer to function.

5.16p5 says:

Otherwise, the result is a prvalue. 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 (13.3.1.2, 13.6). 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 section.

If we change your code as follows:

int x = 20 ;
auto increasing = [&x](int lhs, int rhs){return lhs < rhs;};
auto decreasing = [&x](int lhs, int rhs){return lhs > rhs;};

both gcc and clang generate an error, clang says (see it live):

error: incompatible operand types ('(lambda at prog.cc:8:23)' and '(lambda at prog.cc:9:23)')
std::sort(v.begin(), v.end(), increase ? increasing : decreasing);
                                      ^ ~~~~~~~~~~   ~~~~~~~~~~

For reference the draft C++11 standard 5.1.2 [expr.prim.lambda] says:

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.

The wording is modified in the draft C++14 standard but does not alter this property.

Update

Filed a bug report.

like image 126
Shafik Yaghmour Avatar answered Oct 01 '22 06:10

Shafik Yaghmour