There's no need for enable_if, use expression SFINAE to select the correct overload when the operator<< is present.
namespace detail { template<typename T> auto stringify(std::stringstream& ss, T const& t, bool) -> decltype(ss << t, void(), std::string{}) { ss << t; return ss.str(); } template<typename T> auto stringify(std::stringstream&, T const&, int) -> std::string { return "No overload of operator<<"; } } template <typename T> std::string stringify(const T& t) { std::stringstream ss; return detail::stringify(ss, t, true); }
Live demo
The stringify function template simply delegates to one of the detail::stringify function templates. Then, the first one is selected if the expression ss << t is well-formed. The unnamed bool parameter is being used for disambiguation between the two detail::stringify implementations. Since the primary stringify function passes true as the argument to detail::stringify, the first one will be a better match when the operator<< overload is present. Otherwise the second one will be selected.
This expression decltype(ss << t, void(), std::string{}) in the trailing return type of the first stringify template probably merits a more detailed explanation. Here we have a single expression consisting of 3 sub-expressions separated by the comma operator.
The first one, ss << t is what determines whether that function template passes template parameter substitution and will be added to the overload resolution set. This will occur if the expression is well-formed, i.e. if the type in question overloads operator<<.
The middle sub-expression, void() doesn't do anything other than ensure that some user-defined operator, is not selected (because you cannot overload operator, with a void parameter type).
The third, and rightmost, sub-expression, std::string{} is what determines the return type of the detail::stringify function.