[C++] template question

Started by
11 comments, last by random_thinker 16 years, 6 months ago
Greetings, I've got a class that stores its value as a std::string. This value can be set using a public accessor method that is templated and overloaded to accept many different inputs. The generic 'set' template method takes in ints, shorts, floats, etc, passes the value through a std::stringstream for conversion, then stores the value as a string. I have a couple specialized versions of the 'set' method which handle input that the stringstream cannot. This all works fine. My problem comes with the 'get' method. Originally, my 'get' method required the caller to pass by reference a parameter into the function to know what type to convert to. It looks like:
<type> value;
instance.get(value);
This again allowed me to use specialized methods for the types that stringstream can't convert to. However, it felt cumbersome to have to first declare a variable and then pass it into the 'get' method. I then thought about how would look to have the 'get' method simply return the appropriate type and look like:
<type> value = instance.get<type>();
This works fine for many cases, expect for the specialized ones. I have 2 specializations of the 'set' method, but when I try to declare the specializations of the 'get' method using my new scheme, the compiler errors out saying that the two functions only differ by return types. In other words, I have:
template <typename T>
T get()
{
   std::stringstream s;
   s << m_String.c_str();
   double temp = 0.0f;
   s >> temp;
   return static_cast<T>(temp);
}

std::string get() 
{
   return m_String;
}

MyClass get()
{
   MyClass instance;
   instance.set_text(m_String);
   // More happens, but I think you get the picture.
   return MyClass;
}
Is there no way to tell the compiler which version of 'get' (std::string or MyClass) to use? I am able to do it for the template types, why not these other two? (in other words,
unsigned int value = instance.get<unsigned int>(); // works
float value_2 = instance.get<float>(); // works
std::string value_3 = instance.get<std::string>(); // fails!
Advertisement
Do this:
template <typename T>T get(){   std::stringstream s;   s << m_String.c_str();   double temp = 0.0f;   s >> temp;   return static_cast<T>(temp);}template <>std::string get<std::string>() {   return m_String;}template <>MyClass get<MyClass>(){   MyClass instance;   instance.set_text(m_String);   // More happens, but I think you get the picture.   return MyClass;}
Better yet:

template <typename T>T get(){   return boost::lexical_cast<T>(m_String);}

deathkrushPS3/Xbox360 Graphics Programmer, Mass Media.Completed Projects: Stuntman Ignition (PS3), Saints Row 2 (PS3), Darksiders(PS3, 360)
Read this
@ King Mir: That's exactly what I was looking for, thanks.

@ deathkrush: I'm not sure of the reasoning behind your post. Are you just saying to use the lexical_cast over the stringstream (which I wouldn't argue against), or does using the lexical_cast gain me something else?

@ rozz666: Good article. I'm going to have to keep that one in mind as I'm sure I'll have to come back to it at some point. It's a bit too much for me to fully appreciate at this time since I'm not a wiz with templates.
Why not define the usual stream insertion and extraction operators (<< and >>) for your other types? That way you don't have any special cases.
@ the_edd: Two reasons.

First, I had a problem with std::string. Going from a stringstream to a string using >> causes the copy of data to stop at a space. I want it to copy the entire string. I'm not sure I could even change that behavior.

Second, when using the MyClass example, more things have to happen than simply the >> operator. This then made two specialization functions and I ran into problems.
Quote:
@ deathkrush: I'm not sure of the reasoning behind your post. Are you just saying to use the lexical_cast over the stringstream (which I wouldn't argue against), or does using the lexical_cast gain me something else?


lexical_cast<> uses stringstream, but it includes a whole bunch of added features for specialized casts <template specializations> developed over a period of time by some very smart guys. It also throws an exception when something goes wrong.

It's perfectly OK to use your own function for these types of casts, but if you want a (near) bulletproof approach, use boost::lexical_cast<>.

Quote:
First, I had a problem with std::string. Going from a stringstream to a string using >> causes the copy of data to stop at a space. I want it to copy the entire string. I'm not sure I could even change that behavior.


Use std::getline(std::istream,std::string) to read an entire line at a time, or lookup .rdbuf() to find some ways to read whole files at a time, warts and all. Also it looks like you're over-complicating all the type casts, etc. You could simplify and probably get there easier.

I'm not 100% clear as to what you're trying to do... but you could simplify by trying something like:

template <typename T>T get(){   std::stringstream s;   T cache = 0;   if ( !(s << m_String.c_str()) ); // throw exception   if ( !(s >> cache) ); // throw exception   return cache;}// In fact, if I'm not mistaken, lexical_cast contains the even simpler form:template <typename T>T get(){   std::stringstream s;   T cache = 0;   if ( !(s << m_String.c_str()) || !(s >> cache) ); // throw exception   return cache;}// Which is about as compact as you could get...


--random

[Edited by - random_thinker on October 1, 2007 4:10:05 PM]
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.
The first templated version I gave does basically what you have. The reason I extract the stringstream to a double first is I had an issue when I set the precision of the stringstream low and it converted the input to scientific notation. If I try to extract to an int, it would read '7.7e+006' as '7'. If I extract it do a double, then cast to the int, I would get the correct 7700000. Does that explain the extra conversion I'm doing that you were wondering about?

The problem comes back to the >> operator and std::string. If I use your examples and the given type is a std::string, the >> operator will stop at the first white space. I won't be able to get the behavior I want without making a specialization for the std::string case. You mentioned using std::getline(std::istream,std::string), which is fine, but that's not part of the templated example you gave.
Quote:
...The reason I extract the stringstream to a double first is I had an issue when I set the precision of the stringstream low and it converted the input to scientific notation...


The number that you are seeing when you send the result to output is not what is actually stored, as this should be according to specified type. If you want to view the number in the type form that you want (as opposed to scientific notation which is default, I believe), use stream formatters when you output it. You could do this as a template and feed the type just as you have done with the 'get()' function.

If you have a space in your string representation of a number then the number conversion probably won't work as you wish when stringstream is used. The other option is to step down closer to machine level and write an fsm-type string parser function.

To use std::getline() you just insert your stream and your string and you can read up to the first newline marker by default, or specify your own marker (like a space). std::getline(,) has some advantages over 'std::cin >>' for example in terms of how it leaves the stream state. Use it something like:

std::string cache;std::cout >> "Input a number: ";std::getline(std::cin,cache);// Cache will contain your input. Accepts up to newline, can also use:std::getline(std::cin,cache," ");// To stop at a space.


--random

[Edited by - random_thinker on October 2, 2007 2:02:26 AM]
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.

This topic is closed to new replies.

Advertisement