Actually, to cover all the possible cases converting from and to string, you need a fairly elaborate mechanism. I have pasted a possible implementation below, but that certainly leaves the question why you don't want to use boost::lexical_cast
instead.
//Beware, brain-compiled code ahead!
namespace detail {
template< typename T, typename S >
struct my_lexical_caster {
static T my_lexical_cast(const S& s) {
std::stringstream ss;
if( !(ss << s) ) throw std::bad_cast("cannot stream from source");
T t;
if( !(ss >> t) ) throw std::bad_cast("cannot stream to target");
return t;
}
};
template< typename S >
struct my_lexical_caster<std::string,S> {
static std::string my_lexical_cast(const S& s) {
std::ostringstream oss;
if( !(oss << s) ) throw std::bad_cast("cannot stream from source");
return oss.str();
}
};
template< typename T >
struct my_lexical_caster<T,std::string> {
static T my_lexical_cast(const std::string& s) {
std::stringstream ss(s);
T t;
if( !(ss >> t) ) throw std::bad_cast("cannot stream to target");
return t;
}
};
template< typename T >
struct my_lexical_caster<T,T> {
static const T& my_lexical_cast(const T& s) {return s;}
};
template<>
struct my_lexical_caster<std::string,std::string> {
static const std::string& my_lexical_cast(const std::string& s) {return s;}
};
}
template< typename T, typename S >
inline T my_lexical_cast(const S& s)
{
return detail::my_lexical_caster<T,S>::my_lexical_cast(s);
}
So why is this so complicated?
First, see how we have two template parameters here, one of which determines the return type of my_lexical_cast<>()
. Now we have to provide special implementations for certain special types. While we could overload functions based on different function arguments, we cannot overload them based on return values. So, instead of overloading the function template, we need to specialize the template.
However, this, too, comes with a catch: There is no partial specialization for function templates, only full specialization. The commonly given reason for this is that instead of function template partial specialization we have overloading of function templates. While that might be, it does not help us when return types are involved.
The common way to circumvent the missing function template partial specialization is to use _class template partial specialization instead, as that is available. This is done by creating class templates and implementing the algorithm in a public static member function of it. The class template can then be partially specialized, and each specialization can come with its own implementation of the static member function.
So this explains why there are class templates (actually they are structs, but that's just to save us the trouble of explicitly making their only member public) in the detail
namespace.
And why do we need so many of these specializations?
Well, first, there certainly needs to be a general implementation, that converts from any streamable type to any other.
Then, as you observed, we need one specialization to cover the case where we want to convert into a string, because the default implementation is faulty in this case.
The one that matches the case where both template parameters are of the same type is a pure optimization: if you want to convert an int
into an int
, we can just hand out the original value. You might ask yourself why on earth anyone ever would want to do that, but in template code, where one doesn't know which types the code might be invoked with, things like this happen all the time.
The specialization for converting from a string to any other type is an optimization, too. It avoids streaming the string into the stream, and instead initializes an output string stream directly. This presumes that the latter is actually faster than the former. While I haven't measured this, I think we can safely assume that it is never slower.
That leaves the last one, which "converts" a string into a string. Why do we need this one, isn't that case already covered by the one "converting" and T
into T
? Well, yes, it is, and we could use this one, too, since, semantically, it does the right thing. But the compiler doesn't care about semantics, its only concern is syntax. And when you want to "convert" a std::string
into a std::string
the compiler finds three specializations that all match partially: <T,std::string>
, <std::string,T
, and <T,T>
. Since it doesn't know what to do in this case and throws up an error, we need to help it by providing an unanimously better match than any of these three.