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 =delete
d 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.