Search code examples
c++c++11stltemplate-meta-programmingtype-traits

Need syntax help to specialize class methods based on type of template argument


I have a generic thread-safe queue class that uses std::priority_queue<std::shared_ptr<T>> as the container type (as specified through the template args). However, I would like to alternatively specialize this to use a simpler std::queue<std::shared_ptr<T>>. Unfortunately this would mean using a different technique to retrieve the top-most element of the queue (top() for std::priority_queue<T> or front() for std::queue<T>). I have a live coliru demo that shows a working prototype for priority_queue types. However, if I attempt to use the std::queue by adding the following code:

I get the expected error from the compiler indicating that std::queue has no top() method.

I suspect that the key to getting this right is to use std::enable_if_t<T> for each of the wait_and_pop methods. How would I do this?

EDIT: Thanks to T.C's suggestion, I believe that the generic solution that works for both GCC and MSVC requires the following adjustment to the UtlThreadSafeQueue. There is an updated live coliru demo here:

// Note that overloading 2 function using expression SFINAE
// as shown here does not work on MSVC compiler - it does
// work on later builds of GCC though.
//template<class Q>
//auto front_or_top(Q& q) -> decltype(q.front()) {
//    return q.front();
//}
//template<class Q>
//auto front_or_top(Q& q) -> decltype(q.top()) {
//    return q.top();
//}

// this is a specific overload to work around the missing
// expression SFINAE in MSVC - for the std::queue<T>
template<class T>
auto front_or_top(std::queue<T>& q) -> decltype(q.front()) {
    return q.front();
}

// this is a specific overload to work around the missing
// expression SFINAE in MSVC - for the std::priority_queue<T>
template<class T>
auto front_or_top(std::priority_queue<T>& q) -> decltype(q.top()) {
    return q.top();
}

/**
 * default type of container is a std::queue - which by default
 * uses a std::deque<> of std::shared_ptr<T>'s. The default
 * std::queue<T> does not need to order elements, and as
 * such, it does not require an element comparator as is
 * required by its priority queue counterpart.
 *
 * For the UtlThreadSafeQueue variant that uses a priority
 * queue, we must define a comparator to compare
 * <code>std::shared_ptr<T>'s</code>.
 *
 * To use a UtlThreadSafeQueue with a priority_queue container,
 * use as follows:
 *
 * <pre>{@code
 * // using priority queue
 * UtlThreadSafeQueue<PriorityLevel, std::priority_queue<
 *     std::shared_ptr<PriorityLevel>,
 *     std::vector<std::shared_ptr<PriorityLevel>>,
 *     ptr_less<std::shared_ptr<PriorityLevel>>>> prtyQ;
 *
 * }</pre>
 */
template<typename T, typename Cont = std::queue<std::shared_ptr<T>>>
class UtlThreadSafeQueue {
private:
    mutable std::mutex mut;
    Cont data_queue;
    std::condition_variable data_cond;
    std::size_t capacity;
    std::atomic<bool> shutdownFlag;
public:

Solution

  • Use an overloaded free function helper. One possible way:

    template<class Q>
    auto front_or_top(Q& q) -> decltype(q.front()) { return q.front(); }
    
    template<class Q>
    auto front_or_top(Q& q) -> decltype(q.top()) { return q.top(); }
    

    And then do front_or_top(cont). Note that this is ambiguous if the container defines both front() and top().

    Alternatively, you can also overload front_or_top just for queues and priority_queues:

    template<class T, class Cont>
    auto front_or_top(std::queue<T, Cont>& q) -> decltype(q.front()) { return q.front(); }
    
    template<class T, class Cont, class Comp>
    auto front_or_top(std::priority_queue<T, Cont, Comp>& q) -> decltype(q.top()) { return q.top(); }