Search code examples
c++templatesc++14sfinaeenable-if

Issue with enable_if and multiple conditions


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?


Solution

  • 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.