简体   繁体   中英

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?

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 (eg 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.

One possible way is to add is_vector trait (look here for more details):

template<typename T> struct is_vector : public std::false_type {};

template<typename T, typename A>
struct is_vector<std::vector<T, A>> : public std::true_type {};

And then modify your convertToString function templates as follows:

// 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::is_arithmetic<T>::value && !is_vector<T>::value, std::string>::type convertToString(const T& t) {
    return std::string(t);
}

// Vectors

template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value && is_vector<T>::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;
}

wandbox example

The template with errors marked:

template<class T>
typename std::enable_if<std::__and_<std::__not_<std::is_arithmetic<T>>::type,
//                                                                    ^^^^^^[1]
        std::__not_<std::is_same<T, <T,
//                                  ^^^[2]
       std::vector<typename T::value_type, typename T::allocator_type>>::value
//                                                                     ^^^^^^^[3]
       >>>::value, std::string>::type convertToString(const T& t){
//       ^[4]
    return std::string(t);
}
// [1] nested ::type not needed and ill-formed without typename keyword
// [2] <T, is garbage
// [3] nested ::value ill-formed because std::__not_ argument must be a type
// [4] too many closing angle brackets

The template with errors fixed:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM