Search code examples
c++sfinae

Choose std::queue or std::priority_queue, depending on whether "<" is defined


Inspired by SFINAE to check if std::less will work, I come to this:

template <typename F, typename S, typename = void>
struct QueueImpl {
    using type = std::queue<std::pair<F, S>>;
};

template <typename F, typename S>
struct QueueImpl<F, S, decltype(std::declval<F>() < std::declval<F>())> {
    using type = std::priority_queue<std::pair<F, S>>;
};

template <typename F, typename S>
using Queue = typename QueueImpl<F, S>::type;

with the intention that if < has been defined for type F, Queue will be std::priority_queue and otherwise std::queue. But it seems always to match the std::queue case:

class C {};

int main()
{
    Queue<int, char> q1;
    Queue<C, char> q2;
    std::cout << typeid(q1).name() << std::endl;
    std::cout << typeid(q2).name() << std::endl;
    return 0;
}

which shows both Queue's are std::queue:

St5queueISt4pairIicESt5dequeIS1_SaIS1_EEE
St5queueISt4pairI1CcESt5dequeIS2_SaIS2_EEE

My second attempt uses std::conditional:

template <typename F, typename S>
using Queue = typename std::conditional_t<
    std::is_same_v<bool, decltype(std::declval<F>() < std::declval<F>())>,
    std::priority_queue<std::pair<F, S>>,
    std::queue<std::pair<F, S>>>;

For q1, it successfully chooses std::priority_queue and prints St14priority_queueISt4pairIicESt6vectorIS1_SaIS1_EESt4lessIS1_EE. But when q2 is defined in the same way as the first attempt, the code cannot compile:

error: no match for ‘operator<’ (operand types are ‘C’ and ‘C’)

So how to make the code work?


Solution

  • Your first attempt fails for the following reason. When you use the template, you don't specify the third argument. Hence, it is supplied from the default type in the primary template, namely void. Then, the specialization can only match the argument types if the decltype results in void, whereas when F is int, the type is bool.

    Fixing this is as simple as casting the argument to decltype to void:

    template <typename F, typename S, typename = void>
    struct QueueImpl {
        using type = std::queue<std::pair<F, S>>;
    };
    
    template <typename F, typename S>
    struct QueueImpl<F, S, decltype(void(std::declval<F>() < std::declval<F>()))> {
        using type = std::priority_queue<std::pair<F, S>>;
    };
    
    template <typename F, typename S>
    using Queue = typename QueueImpl<F, S>::type;
    

    Demo.