Sign in to follow this  

[C++] template question

This topic is 3729 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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!

Share this post


Link to post
Share on other sites
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;
}

Share this post


Link to post
Share on other sites
@ 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.

Share this post


Link to post
Share on other sites
@ 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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
Since all of these methods are being used to store and retrieve data from a GUI widget, I don't really know in advance what kind of data the user is going to store in it. It could simply be a line of text, or it could be a numeric value. I would like it to be flexible enough for the user to use it however they want. If they store a number in it, say an unsigned int, they should be able to extract the number in any format, such as a string, an unsigned int, or event a float. Now, if the user enters in a some text with spaces and tries to extract an unsigned int from it, well, that's their fault. It will return something, but probably nothing that makes sense. Trying to convert "Hello World" to an unsigned int doesn't make sense either, so that's up to the user to fix. What does make sense is storing the value '10', whether given as a string, unsigned int, float, etc, and being able to retrieve it as a string, unsigned int, float, etc.

Share this post


Link to post
Share on other sites
Quote:
I don't really know in advance what kind of data the user is going to store in it. It could simply be a line of text, or it could be a numeric value.


But you do know what you need.

If your widget is a calculator, you know that whatever is entered will be a number. Parse it into a number, and store that.

Unless you will perform no logic in your application with the data you read (in which case string would work for all types), you cannot operate with unknown type.

And as soon as you add logic, you constrain yourself to specific types.

Don't try to be smart here. 100,000 does not equal 65,535.

Share this post


Link to post
Share on other sites
I think that in this case I would store everything as a std::string and then use the above cast or better yet boost::lexical_cast<,> to convert it (on-the-fly) when needed. Then when the user wishes to view it in the appropriate form (ie int/float/double/scientific/string) the output stream format would have to be set appropriately for display purposes.

Also, to read in the data, I would personally use "std::getline(,,)" rather than "std::cin >>" as it has fewer stream state issues, and reads the entire line of data. Of course if your user does 'something wrong' then the 'something wrong' will be stored too. However, in the case that a string phrase is entered, rather than just atomic data, the entire phrase will be stored as a string, which is what you are after, I think.

As for some ideas...:


#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

// Helper data aggregate
template<typename T>
struct Data
{
Data():string(),number(0){}
bool store()
{
std::stringstream s;
return ( (s << string) && (s >> number) );
}
std::string string;
T number;
};

// Helper function to display strings...returns ostream.
template<typename T>
std::ostream &operator<<(std::ostream &out,const Data<T> &data)
{
out << "\nString representation : "<< data.string;
out << std::setprecision(4) << std::fixed; // double, 4 decimals.
// out << std::setprecision(4) << std::scientific; // scientific, 4 decimals
// out << std::setprecision(0) << std::noshowpoint; // integer, no decimals.
return (out << "\nNumber representation : "<< data.number);
}

int main(int argc, char* argv[])
{
// Declare your data, could load a vector, list, etc.
Data<double> data;

// Input...
std::cout << "Input some data: ";
if ( !std::getline(std::cin, data.string) ) std::cout << "Oops!, Read error..." << std::endl;
if ( !data.store() ) std::cout << "Not a number..." << std::endl;

// Output...
std::cout << data << std::endl;
}






Output of strings looks something like this...

Input some data: This is a string
Not a number...

String representation : This is a string
Number representation : 0.0000


Output of numbers results in...

Input some data: 238.6

String representation : 238.6
Number representation : 238.6000

--random

Share this post


Link to post
Share on other sites

This topic is 3729 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this