Search code examples
c++templatesdecltypeenable-if

Default template paramters and partial specialization


I'm trying to understand partial template specialization with default arguments. If I remove the cast to void in the is_comparable specialization the value printed is always false, while if I keep the cast to (void) things work fine. Can someone please explain why the cast to void is needed? It likely has to do with matching the default template argument for T3, but I'm trying to get some additional insight here into the caveats with template partial specialization and default template parameters.

template<typename T1, typename T2, typename T3 = void>
struct is_comparable
{
 static const bool value = false;
};

template<typename T1, typename T2>
struct is_comparable<T1, T2, decltype((void)(std::declval<T1>() == std::declval<T2>()))>
{
  static const bool value = true;
};

int main()
{
   cout << boolalpha;
   cout << is_comparable<int, char>::value << endl;
   cout << is_comparable<int, float *>::value << endl;
}

Update: I observe the same behavior even when pointers are used. I would assume the partial specialization would be picked in this case?

template<typename T1, typename T2, typename T3 = void>
struct is_comparable
{
 static const bool value = false;
};

template<typename T1, typename T2>
struct is_comparable<T1*, T2*, decltype((std::declval<T1>() == std::declval<T2>()))>
{
  static const bool value = true;
};

int main()
{
   cout << boolalpha;
   cout << is_comparable<int*, int*>::value << endl;
}

Solution

  • We use primary template for default, so

    • is_comparable<int, char> is is_comparable<int, char, void>
    • is_comparable<int, float *> is is_comparable<int, float*, void>
    • is_comparable<int*, int*> is is_comparable<int*, int*, void>

    Now, your specialization without void cast is mostly (unless special overload of operator==) is

    • is_comparable<T1, T2, bool /* with SFINAE */>

    So doesn't match expected parameters. You would have to use:

    • is_comparable<int, char, bool>
    • is_comparable<int, float *, bool>
    • is_comparable<int*, int*, bool>

    Another way to cast to void is std::void_t:

    template<typename T1, typename T2, typename Enabler = void>
    struct is_comparable : std::false_type {};
    
    template<typename T1, typename T2>
    struct is_comparable<T1, T2,
                         std::void_t<decltype(std::declval<T1>() == std::declval<T2>())>>
        : std::true_type {};