12

Is there a reason that the std::priority_queue's constructor accepts a comparator by constant reference? What if the comparator goes out of scope?

I was thinking about this in the context of possibly moving the comparator as @LightnessRacesInOrbit pointed out!

I am sorry if there has already been a post about this. I have not been able to find it!

Curious
  • 20,870
  • 8
  • 61
  • 146

3 Answers3

9

I have never actually thought about this before, and the const-ref indeed is a bit misleading. However, the function signature was thought up before move semantics came along and it became vogue to accept everything by value. Indeed, the comparator is copied!

[C++14: 23.6.4.1/4]: Effects: Initializes comp with x and c with y (copy constructing or move constructing as appropriate); calls c.insert(c.end(), first, last); and finally calls make_heap(c.begin(), c.end(), comp).

Lambdas are not copy-assignable, but they are copy-constructible, so there is no problem here.

[C++14: 5.1.2/20]: The closure type associated with a lambda-expression has a deleted (8.4.3) default constructor and a deleted copy assignment operator. It has an implicitly-declared copy constructor (12.8) and may have an implicitly-declared move constructor (12.8). [..]

Does this prevent move-construction of the comparator itself? Yes, it does. I'm going to assume that this convention, of taking the comparator by const-ref then copying it, stems from the STL days, way before move semantics. I imagine it wasn't seriously considered to add overloads to take the comparator by value because that adds complexity and you shouldn't have a complex, move-enhancible comparator in the first place (give them state, sure, but not too much). Still, this may be worth raising with the committee if you can come up with a solid use case for moving a comparator.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • Makes me think about why there isn't a move constructor that moves the comparator! – Curious Feb 04 '16 at 01:05
  • 1
    @Curious: If your comparator has a move constructor then it will be invoked!! – Lightness Races in Orbit Feb 04 '16 at 01:06
  • Hmmmm.. I just checked. That is true. I feel like it would be more clear if a rvalue constructor was provided! – Curious Feb 04 '16 at 01:11
  • 1
    @Curious: That's a fair point, actually — that wording is quite misleading and I misinterpreted it. Only the `Container` can be move constructed. In both ctors the `Comparator` is taken by const ref so move constructing is in fact impossible. I guess nobody cares because comparators are supposed to have only very minimal state if any at all. But you might consider proposing a new ctor overload with the committee. – Lightness Races in Orbit Feb 04 '16 at 01:15
  • @Curious: I've added a bit of writing to address this. – Lightness Races in Orbit Feb 04 '16 at 01:22
  • Whoaaaa that would be fancy. How does one put forth such a legendary proposal? – Curious Feb 04 '16 at 01:23
5

It doesn't go out of scope -- it is copy constructed into the container. The description on cppreference.com states:

explicit priority_queue( const Compare& compare = Compare(),
                         const Container& cont = Container() );

Copy-constructs the underlying container c with the contents of cont. Copy-constructs the comparison functor comp with the contents of compare. Calls std::make_heap(c.begin(), c.end(), comp). This is also the default constructor.

There are various other forms of the constructor, but in all cases the internal comparator is either copy- or move-constructed from the one supplied.

paddy
  • 60,864
  • 6
  • 61
  • 103
  • 1
    Well we usually pass lambdas into functions that take their `Callable` by value so I'd be astounded to hear that they're not supposed to be copyable. Can you provide any evidence for this claim? – Lightness Races in Orbit Feb 04 '16 at 01:01
4

std::priority_queue's constructor makes a copy of the comparator provided, so it's not a problem if it goes out of scope.

You can use a lambda as comparator either by using std::function<bool(const T&, const T&)> as comparator type, or directly:

auto comp = [](int x, int y) { return x > y; };
std::priority_queue<int, std::vector<int>, decltype(comp)> q(comp);

You can facilitate this with a helper function:

template<typename T, typename Compare>
auto make_priority_queue(Compare&& comp) {
    return std::priority_queue<T, std::vector<T>, Compare>(std::forward<Compare>(comp));
}

int main() {
    auto q = make_priority_queue<int>([](int x, int y) { return x > y; });
}
Anton Savin
  • 40,838
  • 8
  • 54
  • 90