Use of template function specialization is rarely a good idea. It has awkward syntax, does not behave like people intuitively expect it to behave, does not allow for partial specialization, and is only best in rare corner cases.
There are better ways to do this.
Rather, use ADL (argument dependent lookup) and overloads. Consider using the name to_string if you want to use the std::to_string functions.
If you want, you can wrap this ADL in a way that lets users rely on it without having to do any work.
namespace my_stuff {
namespace details {
template<class...Ts> std::string ToString(Ts const&...) = delete;
// put support for basic and `std` types in this namespace:
std::string ToString(int t) { /* code */ }
template<class T, class A>
std::string ToString( std::vector<T,A> const & v) { /* code */ }
template<class T> std::string ToString_impl(T const& t) {
return ToString(t);
}
}
template<class T> std::string ToString(T const& t) {
return details::ToString_impl(t);
}
}
Next, in the namespace of a type Foo, put ToString(Foo const& foo). It will automatically be found by a call to my_stuff::ToString(foo), even if Foo is not in the namespace my_stuff.
We put std and basic types in my_stuff::details (or whatever you want to call it) because introducing your own functions into namespace std is illegal, and because you cannot use ADL on basic types in the root namespace.
The trick here is ADL. By invoking ToString without an explicit namespace (in details::ToString_impl), the compiler looks for ToString both in the current namespace, and in the namespace of its arguments.
The default =deleted ToString is optional (you can remove it and I think it will still work), and is low priority (as it uses a variardic pack).
If you define ToString in the namespace of a type foo:
namespace SomeNamespace {
struct Foo {};
std::string ToString( Foo const& ) {
return "This is a foo";
}
}
a call to my_stuff::ToString(foo) will find it.
live example.