views:

39

answers:

1

I once saw this nice little snippet of code below, here at SO:

template<typename to_t, typename from_t>
to_t lex_cast(const from_t &arg) {
    to_t ret;
    std::stringstream os;
    os << arg;
    os >> ret;
    return ret;
}

This mimics boost::lexical_cast. Usage:

string tmp = ::lex_cast<string>(3.14);

However, due to the default skipws of formatted streams, the following will not work as expected:

string tmp = ::lex_cast<string>("foo bar"); // will only return foo, but I want entire string

(I expect similar problems with \n's). I have tried to set noskipws, and template specialization but to no avail. Please advise.

+3  A: 

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.

sbi
@boost::lexical_cast : I am a student, and this is for a small project. We have to submit code - and I can't demand that my professor should install boost first. (Ie. the code should be self contained)I will try your code later :)
eisbaw
@eisbaw: Ok, this certainly is a valid reason. (Well, not quite, as I would expect someone teaching C++ to have boost installed on their machines, but then there might be the problem with subtle differences between boost versions. Having taught myself I know that it is hard to stay on top of everything your students throw at you.) I'll add some explanation to my code, to give you an idea why it is so complicated.
sbi
Excellent explanation.
eisbaw
BTW, my bad_cast constructor will not accept strings. Only if I remove the exception-descriptions (ie throw std::bad_cast()), will it compile. Using gcc 4.4.3.
eisbaw
Also, this is not for C++ course, but for a project in a advanced algo course. I'm using this code for a convenience SVG library (output is SVG, duh :)).
eisbaw
@eisbaw: I wouldn't bet my life on `std::bad_alloc` having to have a ctor talking a `const char*` argument. If I've gotten this wrong, I apologize.
sbi