Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambda function, strange behavior

Tags:

c++

c++11

lambda

Let's assume we have the following lambda declared in the global namespace:

auto Less = [](int a,int b) -> bool
{
    return a < b;
}

And the following code which make use of this lambda:

template<typename T>
struct foo
{
    foo(int v){}
    bool operator<(const foo<T>&) const
    {
        return T(1,2);
    }
};

int main()
{
    typedef foo<decltype(Less)> be_less;
    priority_queue<be_less> data;
}

As you can see I use Less as template parameter for the struct foo. With g++4.9.2 this code does not compile:

test1.cpp:13:21: error: no matching function for call to '<lambda(int, int)>::__lambda0(int, int)'
     return T(1,2);
                 ^
test1.cpp:13:21: note: candidates are:
test1.cpp:17:14: note: constexpr<lambda(int, int)>::<lambda>(const<lambda(int, int)>&)
 auto Less = [](int a,int b) -> bool
          ^
test1.cpp:17:14: note:   candidate expects 1 argument, 2 provided
test1.cpp:17:14: note: constexpr<lambda(int, int)>::<lambda>(<lambda(int, int)>&&)
test1.cpp:17:14: note:   candidate expects 1 argument, 2 provided

Bu I may fix this problem adding two small changes, first of all I need to change the lambda into this:

bool Less = [](int a,int b) -> bool
{
    return a < b;
}

As you can see i just replaced auto with bool, this modification alone still does not work:

test1.cpp:13:21: error: expression list treated as compound expression in functional cast [-fpermiss
ive]
     return T(1,2);

Unless I add -fpermissive, or i may change the operator< bool in this way:

bool operator<(const foo<T>&) const
{
    return T((1,2));
}

Note the double parentheses. Now the code compile and everything work.

My question is, which is the technical reason why auto Less does not work but bool Less work?

I believe I know why the double parentheses are required in the second operator<, this should be to avoid the compiler to interpret T(1,2) as a declaration instead of a call.

Thanks for your time

like image 640
fjanisze Avatar asked Feb 10 '23 18:02

fjanisze


1 Answers

In your first example you're constructing foo<T> where [T = decltype(Less)]. So in this expression

return T(1,2);

you're trying to construct an instance of the lambda by invoking a constructor that takes 2 ints, which obviously doesn't exist. That's exactly what the error message is telling you

error: no matching function for call to '<lambda(int, int)>::__lambda0(int, int)'

The only constructors that exist are the copy and move constructors for the lambda (the closure type created from a lambda expression is not default constructible), which the compiler tries to match the arguments against and fails

constexpr<lambda(int, int)>::<lambda>(const<lambda(int, int)>&)
constexpr<lambda(int, int)>::<lambda>(<lambda(int, int)>&&)
candidate expects 1 argument, 2 provided

In the second case, by making this change

bool Less = [](int a,int b) -> bool
{
    return a < b;
};

what you've done is declare a boolean variable named Less and initialize it to true.

This is because the lambda expression you have is capture-less, which means it can be implicitly converted into a pointer to a function that takes the same arguments as the lambda's operator() and returns the same type as the original lambda. So you convert the lambda expression to bool(*)(int,int).

Next, a function pointer is implicitly convertible to bool and will always evaluate to true (assuming it actually points to the address of a function, which it does here). So Less gets initialized to true.

Now, decltype(Less) is nothing but bool. So here you're trying a function style cast to bool, but passing in 2 arguments

return T(1,2);

Hence the error

error: expression list treated as compound expression in functional cast

By adding the extra parentheses you have an expression consisting of 2 sub-expressions separated by the comma operator. This will evaluate and discard the first sub-expression (1) and return the value of the second (2) which is then cast to bool, so it converts to true.


I'm not really sure what you're attempting to do to be able to suggest a solution, but if all you want is define some comparison predicate for foo that'll then be invoked by the priority_queue, then maybe the following works?

struct foo
{
    foo(int v) {}
};

auto Less = [](foo const& a, foo const& b) -> bool
{
    return true; // do whatever comparison you need 
};

int main()
{
    using my_priority_queue = std::priority_queue<foo, std::vector<foo>, decltype(Less)>;

    my_priority_queue data(Less); // pass a copy of the comparator 
}

Live demo

like image 173
Praetorian Avatar answered Feb 13 '23 07:02

Praetorian