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