Problem with one-liner string conversion

J

jl_post

Hi,

A few months back I remember reading through C++ newsgroups trying
to find a way to quickly convert a number to a C++ std::string. I
often see code like:

// Create a string that holds a number:
int num = 7; // for example
char tmp[24];
sprintf(tmp, "%d", num);
const std::string str = tmp;

but I wanted to see if there was a better way. I generally would use
this:

// Create a string that holds a number:
int num = 7;
std::eek:stringstream outStream; // #include <sstream>
outStream << num;
const std::string str = outStream.str();

and this works great, but just like the first example, it declares and
uses a temporary variable/object. What I'd have liked to see was some
sort of method in std::string that handled the conversion in one line,
like this:

// NOTE: not real code!
int num = 7;
const std::string str = std::string::number(num);

While I didn't find what I was looking for, I did find an
interesting post, where someone posted this nice, simple function:

// Converts many things to a std::string:
#include <sstream>

template <typename T>
std::string toStdString(const T &thing)
{
std::eek:stringstream os;
os << thing;
return os.str();
}

Using this function, I can now write code like this:

int num = 7;
const std::string str = toStdString(num);

This code is very clear, but it also has another benefit: Not only
can I easily convert integers and floats to a std::string, but I can
convert pretty much anything that I can send to std::cout.

But then I thought of something: Why even define a templated
function? I could write code like this:

int num = 7;
std::string str = (std::eek:stringstream() << num).str();

It may not be as elegant as the toStdString() solution, but at least
it won't require me to define (or include a header file of) the
toStdString() function in every program I write. In addition, there's
no need to introduce a temporary variable to the current scope.

However, when I tried to compile this code, I got the following
error message:

testfile.cpp: In function `int main(int, char**)':
testfile.cpp:23: error: 'struct std::basic_ostream<char,
std::char_traits<char> >' has no member named 'str'

I can't figure out why I'm getting this error message. I mean, I'm
declaring a std::eek:stringstream object, using the "<<" operator on it
(which, I believe, returns the very same std::eek:stringstream object),
and then I call the ::str() method on it. std::eek:stringstream HAS to
have ::str() defined, otherwise the toStdString() function above
wouldn't compile.

What puzzles me is that I've successfully compiled similar things
with std::strings, like this:

// Prints "Hello, world!":
const char* word1 = "Hello";
const char* word2 = "world";
printf("%s", (word1 + std::string(", ") + word2 + "!\n").c_str());

and have had no problems with it. (Basically, word1+std::string(", ")
+word2+"!\n" returns a std::string, and the ::c_str() method is called
on it, passing its internal data to printf() to use before the
std::string goes out of scope.)

I would think that what I'm trying to do with std::eek:stringstream is
really no different, so it puzzles me why (std::eek:stringstream() <<
num).str() generates a compiler error. Does anybody know why this
generates a compiler error?

Thanks in advance for any help.

-- Jean-Luc
 
K

Kai-Uwe Bux

Hi,

A few months back I remember reading through C++ newsgroups trying
to find a way to quickly convert a number to a C++ std::string. I
often see code like:

// Create a string that holds a number:
int num = 7; // for example
char tmp[24];
sprintf(tmp, "%d", num);
const std::string str = tmp;

but I wanted to see if there was a better way. I generally would use
this:

// Create a string that holds a number:
int num = 7;
std::eek:stringstream outStream; // #include <sstream>
outStream << num;
const std::string str = outStream.str();

and this works great, but just like the first example, it declares and
uses a temporary variable/object. What I'd have liked to see was some
sort of method in std::string that handled the conversion in one line,
like this:

// NOTE: not real code!
int num = 7;
const std::string str = std::string::number(num);

While I didn't find what I was looking for, I did find an
interesting post, where someone posted this nice, simple function:

// Converts many things to a std::string:
#include <sstream>

template <typename T>
std::string toStdString(const T &thing)
{
std::eek:stringstream os;
os << thing;
return os.str();
}

Using this function, I can now write code like this:

int num = 7;
const std::string str = toStdString(num);

This code is very clear, but it also has another benefit: Not only
can I easily convert integers and floats to a std::string, but I can
convert pretty much anything that I can send to std::cout.

But then I thought of something: Why even define a templated
function? I could write code like this:

int num = 7;
std::string str = (std::eek:stringstream() << num).str();

It may not be as elegant as the toStdString() solution, but at least
it won't require me to define (or include a header file of) the
toStdString() function in every program I write. In addition, there's
no need to introduce a temporary variable to the current scope.

However, when I tried to compile this code, I got the following
error message:

testfile.cpp: In function `int main(int, char**)':
testfile.cpp:23: error: 'struct std::basic_ostream<char,
std::char_traits<char> >' has no member named 'str'

Note that the operator<< you are invoking is inherited from the
std::basic_ostream<char>. Its return type is std::basic_ostream&. At that
point, all information about the underlying object being a string stream is
lost. An std::basic_ostream<char> object does not have a str() member
function. You need to cast back:

std::string str =
static_cast< std::eek:stringstream& >( std::eek:stringstream() << num ).str();


[snip]


BTW: you should just put toStdString into your library and use it. It is
much better than writing a cryptic line every time you want to convert
something. Also, you are creating a stringstream object in either case.
Whether it is a named local variable of a temporary doesn't really matter
all that much.


Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

Kai-Uwe Bux said:
[snip]
int num = 7;
std::string str = (std::eek:stringstream() << num).str();

It may not be as elegant as the toStdString() solution, but at least
it won't require me to define (or include a header file of) the
toStdString() function in every program I write. In addition, there's
no need to introduce a temporary variable to the current scope.

However, when I tried to compile this code, I got the following
error message:

testfile.cpp: In function `int main(int, char**)':
testfile.cpp:23: error: 'struct std::basic_ostream<char,
std::char_traits<char> >' has no member named 'str'

Note that the operator<< you are invoking is inherited from the
std::basic_ostream<char>. Its return type is std::basic_ostream&. At that
point, all information about the underlying object being a string stream
is lost. An std::basic_ostream<char> object does not have a str() member
function. You need to cast back:

std::string str =
static_cast< std::eek:stringstream& >( std::eek:stringstream() << num ).str();
[snip]

Ah, and just for kicks, now try:

#include <sstream>
#include <iostream>

int main ( void ) {
char const * message = "Hello world.\n";
std::cout <<
static_cast< std::eek:stringstream& >
( std::eek:stringstream() << message ).str();
}

and interpret the output.


I re-iterate: go with the toStdString() solution.


Best

Kai-Uwe Bux
 
M

Martin York

Kai-Uwe Bux said:
(e-mail address removed) wrote:
[snip]
   int num = 7;
   std::string str = (std::eek:stringstream() << num).str();
It may not be as elegant as the toStdString() solution, but at least
it won't require me to define (or include a header file of) the
toStdString() function in every program I write.  In addition, there's
no need to introduce a temporary variable to the current scope.
   However, when I tried to compile this code, I got the following
error message:
testfile.cpp: In function `int main(int, char**)':
testfile.cpp:23: error: 'struct std::basic_ostream<char,
std::char_traits<char> >' has no member named 'str'
Note that the operator<< you are invoking is inherited from the
std::basic_ostream<char>. Its return type is std::basic_ostream&. At that
point, all information about the underlying object being a string stream
is lost. An std::basic_ostream<char> object does not have a str() member
function. You need to cast back:
  std::string str =
  static_cast< std::eek:stringstream& >( std::eek:stringstream() << num ).str();

[snip]

Ah, and just for kicks, now try:

#include <sstream>
#include <iostream>

int main ( void ) {
  char const * message = "Hello world.\n";
  std::cout <<
    static_cast< std::eek:stringstream& >
    ( std::eek:stringstream() << message ).str();

}

and interpret the output.

I re-iterate: go with the toStdString() solution.

Best

Kai-Uwe Bux


Or get a hold of the boost libraries (boost.org) and use its
lexical_cast<>(). It not only converts from string to number and
number to string. But from anything to anything. As long as you can
use a stream to convert them. Even better boost is the semi-offical
testing ground for stuff that will be considered for the next
standard. So it is highly tested and widely addopted.

#include <boost/lexical_cast.hpp>

int main()
{
std::string val("567");
int num = boost::lexical_cast<int>(val);
}
 
J

James Kanze

Kai-Uwe Bux said:
(e-mail address removed) wrote: [snip]

Or get a hold of the boost libraries (boost.org) and use its
lexical_cast<>(). It not only converts from string to number
and number to string. But from anything to anything.

I'm not sure that I'd consider that an advantage. It doesn't
make sense to convert anything to anything (and going through
the textual representation, which boost::lexical_cast does, is
not necessarily the correct way to convert x to y).

Even in the case of conversion to string, I'm not sure just how
often a generic solution is appropriate; you probably don't want
to format a value representing an interest rate (percent) in the
same way you'd format a value representing a monetary value.
 
M

Martin York

I'm not sure that I'd consider that an advantage.  It doesn't
make sense to convert anything to anything (and going through
the textual representation, which boost::lexical_cast does, is
not necessarily the correct way to convert x to y).

Context is everything.
I hope (and imagine) other readers did not read into my last reply
that I was suggesting that lexical_cast<>() should be used to convert
from a type to any other arbitory type. Where appropriate stream can
be used (via lexical_cast<>()) in a type safe way to convert types
appropriately (Its usage will ALWAYS depend on the context of the
problem). 'Normally' this will be <Type> => String or String =>
<Type>. But as with all rules and complex systems it usage is not
limited to just that.

Martin
 
J

jl_post

Thank you all for all your help. I really appreciate it.

But before I end this post, let me answer a question some have been
asking:

What's wrong with the helper function approach?

Nothing, really. I just wanted to know why the line:

std::string str = (std::eek:stringstream() << num).str();

wouldn't compile. That line to me is fairly simple (even thought some
might call it "ugly"), that if I were to find a just-as-simple
approach, I'd use that.

However, I agree that the fix that uses the static_cast:

std::string str =
static_cast<std::eek:stringstream&>
(std::eek:stringstream() << num).str();

is not that "pretty" and that there are more readable ways to do the
same thing.

(Basically, I wanted to know why the code wouldn't compile. I
didn't mean to imply that I rejected the helper-function approach.
Sorry for the confusion.)

Again, thanks for all your help.

-- Jean-Luc
 
J

James Kanze

Context is everything.
I hope (and imagine) other readers did not read into my last
reply that I was suggesting that lexical_cast<>() should be
used to convert from a type to any other arbitory type. Where
appropriate stream can be used (via lexical_cast<>()) in a
type safe way to convert types appropriately (Its usage will
ALWAYS depend on the context of the problem). 'Normally' this
will be <Type> => String or String => <Type>. But as with all
rules and complex systems it usage is not limited to just
that.

Yes. The problem with lexical_cast is that it is too
convenient; it sort of works in a large number of cases where it
probably shouldn't (and then fails for some particular values
which you forgot to test).
 
V

Vidar Hasfjord

   std::string str = (std::eek:stringstream() << num).str();

wouldn't compile.  That line to me is fairly simple (even thought some
might call it "ugly"), that if I were to find a just-as-simple
approach, I'd use that.

Using a utility class, a string builder, you can achieve this terse
solution:

string s = SB << num;

See here for discussion, description and limitations:

http://groups.google.com/group/comp.lang.c++.moderated/msg/a400421feca71ea1

Regards,
Vidar Hasfjord
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top