My take on this: you can take the metafunction you found as is, it works nicely. Let's still discuss briefly why it is working:
sizeof
does not actually evaluate an expression; it deduces its type and returns the size of that type. Type sizes are implementation defined and we cannot assume much about them, but we know that sizeof(char) != sizeof(char[2])
, so we use these types to test for.
We define a stream operator at namespace level using an any_t
type, that will accept - you guessed it - any type and let it return something (it's not actually important what type, as long as it's not ostream &
). This is what we fall back to if the type does not have a stream operator defined. In the class itself we now define two functions, one taking an ostream &
, which will be the result if the stream operator is defined, and one taking the return type we defined for our fallback function.
We can now test sizeof(test(s << c))
which, again, will not evaluate the expression, only determine the return type and return its size.
Now all that we understand how that works, all there's left to do is to embed this into our application. There are several approaches to do this; one way, which also works prior C++11 is to use a functor:
template <bool, typename T>
struct to_string_functor
{
std::string operator()(T const & t) const
{
std::stringstream ss;
ss << t;
return ss.str();
}
};
template <typename T>
struct to_string_functor<false, T>
{
std::string operator()(T const &) const
{
return typeid(T).name();
}
};
template <typename T>
struct foo
{
std::string to_string() const
{
return to_string_functor<
has_insertion_operator<T>::value, T
>()(m_Value);
}
/* ... */
};
There are more ways to do this, another one being enable_if
, if C++11 is available for you (you will probably want partially specialized functions to do this); you may want to read this excellent blog post on this matter.
In this simple case however, a functor should do in my opinion.