I have a fairly large, sophisticated algorithm that uses a std::priority_queue. In some cases, I want the queue to be a min-queue, and in others, a max-queue. So I have to provide a comparator type that does one or the other. Since the comparator is a template parameter, it is fundamentally part of the type of the std::priority_queue. So everywhere the queue is referenced, the code must know the type.
One option, of course, is to provide a custom comparator that has state, and selects between ascending and descending on each invocation of operator(), but I’m trying to avoid the performance overhead of having this branch for every comparison. The other alternative that I can see is to duplicate the entire algorithm, one for sorting ascending, and the other for descending.
Is there a better alternative to this problem?
EDIT: Since some people seem to be so eager to help without really understanding the problem here, I’ll try to elaborate a bit.
What I’ve implemented is an external sort algorithm (external merge sort). The algorithm uses a std::priority_queue during the merge phase. Now, the sort can either be ascending or descending. The std::priority_queue comparator must be different for those two cases. You might imagine that a simple sort algorithm could be easily parameterized to handle ascending/descending like such:
// psuedocode...
if (ascending) {
if (a > b) // do something
} else {
if (a < b) // do something
}
One problem with this is that the ascending flag has to be checked every iteration of the sort loop. ‘jalf’ suggests the compiler can “inline” this branch, but I don’t think I’ve seen it happen, at least not with VC++ (yes, I’ve looked at some assembly).
Now, when using a std::priority_queue to keep some things in sorted order, the actual type of the queue is different for ascending and descending sorts (since the comparator must be different, and it is a template parameter). So the options are:
- Duplicate the algorithm (function) for the descending case.
- Parameterize the algorithm on sort order and use one queue for ascending and a different one for descending.
- Use a comparator functor with a single piece of state (isAscending) so that
operator()()can perform the right comparison for ascending and descending. - Templatize the function and require the comparator be passed as a template argument.
- Use a function pointer as the comparator template argument.
Please note that I am not switching between two different queue behaviors during the algorithm (as some have asked). I am setting up the queue as a min or max queue at the start of the algorithm, and it remains as such for that particular sort.
Now to compare the options above.
Option 1 is out because of unwanted code duplication and maintenance (although it would likely produce the best performing code).
Option 2 is not desirable because of all the additional runtime checks and branching, and the unnecessary creation of a queue that will not be used.
Option 3 is more attractive because the branching is encapsulated/isolated in the comparator functor — but still requires a runtime check for each comparison (this is fundamentally what I wanted to avoid).
Option 4 pushes the problem up one level and requires the caller to know and have access to the comparator functors — kind of messy.
Option 5 seems pretty good — it makes for clean code by allowing the comparator function to differ at runtime without changing the type of the queue. The decision logic does not have to be exposed to the caller (as it would with option 4).
The only negative I can see with option 5 is that the compiler may not be able to inline the call through the function pointer — but in my case it did, because the called function was defined locally in the same translation unit. If it were not inlined, I’m guessing option 3 would have been better, but in my case performance was better with option 5.
Also, after I realized that option 5 was possible (I didn’t at first), it occurred to me that using function pointers instead of functors is probably not done much, and I suspect people sometimes jump through hoops to use functors (as I would have had to), when a function pointer may make for much cleaner code (particularly when performance isn’t a concern).
Use templates
— edit
I read your edit but I still don’t really get what’s bothering you. You say
Anyway the caller will have to choose between ascending or not.
And, of course, it is not necessary for him to know which comparator type to use:
Or even