std::swap of std::priority_queue with lambda comparator compiles under C++20 but not C++17: "error: object of type value_compare cannot be assigned"

Advertisements

The following code, which std::swaps two std::priority_queue<int, std::vector<int>, decltype(some lambda)> results in a compiler error in C++17, but not in C++20.

#include <queue>

int main()
{
    auto cmp = [](int x, int y) {return x > y;};
    std::priority_queue<int, std::vector<int>, decltype(cmp)> pq1(cmp), pq2(cmp);
    std::swap(pq1, pq2); // adding this line results in an error
    return 0;
}

The error under C++17 is

error: object of type 'value_compare' (aka '(the lambda cmp)') cannot be assigned because its copy assignment operator is implicitly deleted
    {c = _VSTD::move(__q.c); comp = _VSTD::move(__q.comp); return *this;}

note: in instantiation of member function 'std::priority_queue<int, std::vector<int>, (lambda at main.cpp:13:16)>::operator=' requested here
  __x = _VSTD::move(__y);

note: in instantiation of function template specialization 'std::swap<std::priority_queue<int, std::vector<int>, (lambda at main.cpp:13:16)>>' requested here
    swap(pq1, pq2);

I feel the key here is that somehow, the lambda cmp’s copy assignment operator is implicitly deleted. I’ve learned from the top answer at std::priority_queue syntax with lambda is confusing that C++20 made lambdas without default captures default-constructible (see cppreference source as well), but the issue here seems to be with copy-constructing, not default-constructing.

Why doesn’t the code compile under C++17, and what changes in C++20 let it compile?

>Solution :

"seems to be with copy-constructing": No, as the error message says it is with copy-assigning.

Swapping requires reassignment between the two objects, not only construction, because the two objects are not replaced by new ones. Only their values are exchanged.

Before C++20 lambdas were not assignable at all and so this can’t work. Since C++20 lambdas without capture are copy-assignable and so this works.


If you need a workaround for C++17, you can convert a non-generic lambda without captures to a function pointer which is copy-assignable (and -constructible). The function pointer conversion can be done explicitly or implicitly with the + trick:

auto cmp = +[](int x, int y) {return x > y;};
std::priority_queue<int, std::vector<int>, decltype(cmp)> pq1(cmp), pq2(cmp);
std::swap(pq1, pq2); // works now

In C++20 you also don’t need to actually pass cmp to the constructors, because lambdas without capture are now also default-constructible as you mentioned.

Leave a Reply Cancel reply