Issue with enable_if and multiple conditions

Overblade picture Overblade · Jan 8, 2017 · Viewed 7.5k times · Source

I tried to implement a function which converts a generic type to a string. Integral types need to be converted using std::to_string(), strings and chars using std::string() and vectors, element by element, to a string using one of the other methods (depending on their content).

This is what I have:

//Arithmetic types    

template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type convertToString(const T& t){
    return std::to_string(t);
}

//Other types using string ctor

template<class T>
typename std::enable_if<std::__and_<std::__not_<std::is_arithmetic<T>>::type,
        std::__not_<std::is_same<T, <T,
       std::vector<typename T::value_type, typename T::allocator_type>>::value
       >>>::value, std::string>::type convertToString(const T& t){
    return std::string(t);
}

//Vectors

template<class T>
typename std::enable_if<std::is_same<T, std::vector<typename T::value_type, 
   typename T::allocator_type>>::value, std::string>::type convertToString(const T& t){
    std::string str;
    for(std::size_t i = 0; i < t.size(); i++){
        str += convertToString(t[i]);
    }
    return str;
}

The problem is that the 2nd function does not compile. How can I design the 2nd function so that it does compile (and work) and does not create ambiguity issues?

Answer

Barry picture Barry · Jan 8, 2017

Oktalist's answer explains why your type trait doesn't compile. Also, you shouldn't use __and_ and __not_. Those are reserved and could easily change in the next compiler version. It's easy enough to implement your own version of those traits (e.g. see the possible implementation of conjunction).

I would suggest an entirely different approach. We can use choice<> to make overloading these cases far simpler:

template <int I> struct choice : choice<I+1> { };
template <> struct choice<10> { };

Via:

// arithmetic version
template <class T>
auto convertToStringHelper(T const& t, choice<0> )
    -> decltype(std::to_string(t))
{
    return std::to_string(t);
}

// non-arithmetic version
template <class T>
auto convertToStringHelper(T const& t, choice<1> )
    -> decltype(std::string(t))
{
    return std::string(t);
}

// vector version
template <class T, class A>
std::string convertToStringHelper(std::vector<T,A> const& v, choice<2> )
{
    // implementation here
}

template <class T>
std::string convertToString(T const& t) {
    return convertToStringHelper(t, choice<0>{});
}

This is nice because you get all the SFINAE without any of the enable_if cruft.