2
votes

I'm trying to specialize a function for a range of types using std::enable_if.
Here is a a simpler version of what I'm trying to accomplish.

#include <type_traits>
#include <string>

struct Ip
{};

template <typename T>
bool Equal(const std::string& s, const T& data)
{return s == data;}

template <typename T>
bool Equal(const std::string& s, const typename std::enable_if<std::is_integral<T>::value, T>::type& data)
{
    //int specific
}

template <typename T>
bool Equal(const std::string& s, const typename std::enable_if<std::is_floating_point<T>::value, T>::type& data)
{
    //Float specific
}

//Specialization
template <> bool Equal(const std::string& s, const Ip& data)
{
   //Ip specific
} 

int main()
{
    //Equal("21",21); // Do not compile
    Equal("21","42.5"); // Compile
}

but when trying to compile, the template functions with std::enable_if does not seem to participate in the resolution, and so the compiler tells me that there is no function that match my function call. I tried using std::enable_if with the return type, but no luck there either. I'm sure there is something I'm doing terribly wrong in this code, or maybe I'm trying to do this the wrong way.
I'm trying to not write every int specialization (int, short, long, long long, ...), so does anyone have a solution for this ?

2
I think why it does not work explained here gotw.ca/publications/mill17.htmSlava
@Slava: He doesn't appear to be attempting to use template specializations.Mooing Duck
@Slava: good link for the last case (specialization for Ip), but other case has other problem.Jarod42

2 Answers

4
votes

I'd make several changes to your code, beginning with the unconstrained function template. Since you're handling arithmetic types separately, that should only be selected if T is neither integral nor floating point.

template <typename T>
typename std::enable_if<!std::is_integral<T>::value &&
                        !std::is_floating_point<T>::value, bool>::type 
    Equal(const std::string& s, const T& data)
{return s == data;}

Now, the way you've defined Equal for integral and floating point types causes the T to be a non-deduced context. So move the enable_if to a dummy template parameter to allow deduction (you could also use the return type, as above).

template <typename T,
          typename std::enable_if<std::is_integral<T>::value, T>::type* = nullptr>
bool Equal(const std::string& s, const T& data)
{
    //int specific
    return false;
}

template <typename T,
          typename std::enable_if<std::is_floating_point<T>::value, T>::type* = nullptr>
bool Equal(const std::string& s, const T& data)
{
    //Float specific
    return false;
}

Finally, there's no need to specialize for handling Ip, create an overload instead.

//Overload
bool Equal(const std::string& s, const Ip& data)
{
   //Ip specific
    return false;
}

Live demo

3
votes

Following works: (https://ideone.com/G1vJKm)

#include <string>
#include <type_traits>

struct Ip
{
    std::string ip;
};

template <typename T>
typename std::enable_if<!std::is_integral<T>::value && !std::is_floating_point<T>::value, bool>::type
Equal(const std::string& s, const T& data)
{return s == data;}

template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
Equal(const std::string& s, const T& data)
{
    //int specific
    return false;
}

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, bool>::type
Equal(const std::string& s, const T& data)
{
    //Float specific
    return false;
}

bool Equal(const std::string& s, const Ip& data)
{
   //Ip specific
    return false;
}

In your case,

template <typename T>
bool Equal(const std::string& s, const T& data)

is a valid candidate for all T, also for integral and floating point.

Your other template functions, compiler can't deduce type T for typename std::enable_if<std::is_integral<T>::value, T>::type.

And for the Ip version a simpler overload is sufficient (and preferred).