Implementing a Custom C++ Priority Queue with Functor Comparators

Function objects, frequently termed functors, allow C++ classes to emulate callable routines by overloading the operator(). This design pattern enables object instantiation that accepts parameters and yields return values identically to standard functions, while preserving encapsulated state and type safety. Unlike conventional function pointers, the overloaded call operator supports arbitrary signatures, making it ideal for generic algorithms that require customizable behavior.

In the context of a priority queue, which operates fundamentally as a binary heap, comparators dictate the ordering strategy. Encapsulating relational logic within dedicated functors eliminates the need to modify core heap traversal routines when switching between ascending and descending sequences.

template <typename T>
struct DescendingOrder {
    bool operator()(const T& left, const T& right) const {
        return left > right;
    }
};

template <typename T>
struct AscendingOrder {
    bool operator()(const T& left, const T& right) const {
        return left < right;
    }
};

A generic priority queue can be constructed using these comparators as template parameters. The underlying storage typically defaults to a sequential container, while the policy parameter determines whether the structure behaves as a max-heap or min-heap.

#include <vector>
#include <cstddef>
#include <utility>

template <typename ValueType, typename Storage = std::vector<ValueType>, typename Comparator = DescendingOrder<ValueType>>
class CustomPriorityQueue {
private:
    Storage elements;
    Comparator compare;

    void sift_up(std::size_t idx) {
        while (idx > 0) {
            std::size_t parent_idx = (idx - 1) >> 1;
            if (compare(elements[parent_idx], elements[idx])) {
                std::swap(elements[parent_idx], elements[idx]);
                idx = parent_idx;
            } else {
                return;
            }
        }
    }

    void sift_down(std::size_t idx, std::size_t count) {
        while (true) {
            std::size_t left_child = (idx << 1) + 1;
            std::size_t right_child = left_child + 1;
            std::size_t target = idx;

            if (left_child < count && compare(elements[target], elements[left_child])) {
                target = left_child;
            }
            if (right_child < count && compare(elements[target], elements[right_child])) {
                target = right_child;
            }
            if (target == idx) {
                break;
            }
            std::swap(elements[idx], elements[target]);
            idx = target;
        }
    }

public:
    CustomPriorityQueue() = default;

    template <typename Iterator>
    CustomPriorityQueue(Iterator begin, Iterator end) {
        for (Iterator it = begin; it != end; ++it) {
            elements.push_back(*it);
        }

        std::size_t n = elements.size();
        for (std::size_t i = n / 2; i > 0; --i) {
            sift_down(i - 1, n);
        }
    }

    void push(const ValueType& val) {
        elements.emplace_back(val);
        sift_up(elements.size() - 1);
    }

    void pop() {
        if (elements.empty()) return;
        std::swap(elements.front(), elements.back());
        elements.pop_back();
        if (!elements.empty()) {
            sift_down(0, elements.size());
        }
    }

    const ValueType& top() const {
        return elements.front();
    }

    bool empty() const {
        return elements.empty();
    }

    std::size_t size() const {
        return elements.size();
    }
};

The insertion operation appends the new value to the underlying container and immediately restores the heap property by propagating the element upward until the comparator condition is no longer satisfied. Conversely, removal extracts the root element, replaces it with the final node, and re-establishes order by pushing the new root downward through the hierarchy. The range constructor efficiently linearizes an arbitrary sequence into a valid heap by iterating backward from the last non-leaf node, applying the downward restoratino routine at each step to guarantee logarithmic initialization complexity.

Tags: C++ priority queue functors Data Structures Templates

Posted on Thu, 01 Oct 2026 16:03:25 +0000 by perrij3