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.