Re: Adding streaming operators to std::string, your opinion?
* Francesco:
On 2 Set, 00:00, "Alf P. Steinbach" <al...@start.no> wrote:
Francesco, entul...@gmail.com says...
[snip]
The topic about printing integers into a string raised this thought
of mine: is it "good" to add streaming operations to regular
strings?
[snip]
When it's used for concatenation it can be much more efficient and much more
clear and readable than some non-operator approach.
[snip]
where one should note that for a typical implementation of string concatenation
'+' operator the latter is less clear and to boot is generally quadratic time,
O(e^2), in the number of string elements e to be concatenated -- while the
former is O(e) for any reasonable string implementation (in place modification).
So, provided it's done for a good reason, and done correctly, using << to
concatenate strings can be a very good idea.
First of all, thanks everybody for your posts.
Every point of view is interesting, sorry if I'm not replying
individually to everyone.
@ Alf: please let me understand your point.
I've tried to implement and profile at once all three possibilities
(inserters, toString function with the default + operator, direct
stringstream usage). To be fair between the measurements, I've helped
the "inserters" version as much as possible (since the toString needs
to be called fewer times).
Well, you haven't (see below).
But it doesn't matter here.
It turns out that the two versions get more or less equal timings - in
my implementation, of course.
Not at all. Your timings show the inserters to be superior. And among the two
inserter approaches, which are fastest, the one that doesn't do a lot of extra
needless work per insertion, is -- not unexpectedly! -- slightly faster.
I suppose you were speaking about an ad-hoc string class that includes
inserters from the beginning, but then that would be something like an
ostringstream,
It would, yes. :-)
while my idea was just to add some local helper
function to stream into a std::string for simpler-notation's sake.
Generally that's a good idea, but invoking std::ostringstream in each << and
then for each such result extracting a string and copying it plus a
concatenation, is ungood for efficiency because, as Jerry remarked else-thread
or was it up-thread, you're then doing much extra, needless work.
Instead do something like (off the cuff)
class S
{
private:
std::ostringstream myStream;
public:
S() {}
S( char const s[] ): myStream( s ) {}
S( std::string const& s ): myStream( s ) {}
template< typename T >
S& operator<<( T const& v )
{
myStream << v;
return *this;
}
std::string str() const { return myStream.str(); }
operator std::string () const { return myStream.str(); }
};
What am I missing about the quadratic difference you spoke about?
You haven't tested it. Your timings are dominated by the inefficient conversion
from number to string, and you have very short strings and low number of them,
avoiding all three factors that might make that difference count. It's possible
that you'll see it if you use e.g. sprintf to implement the conversion, and/or
if you use longer strings, and/or if you have more of them.
Here is my code...
-------
#include <iostream>
#include <sstream>
//#include "profiler.h"
using namespace std;
template<class T>
inline string& operator<<(string& s, const T& t) {
ostringstream stream;
stream << t;
return s += stream.str();
}
inline string& operator<<(string& s, const string& s2) {
return s += s2;
}
inline string& operator<<(string& s, const char* cstr) {
return s += cstr;
}
inline string& operator<<(string& s, char ch) {
return s += ch;
}
template<class T> inline string toString(const T& t) {
ostringstream stream;
stream << t;
return stream.str();
}
const string str = "string";
string UseInserter() {
// profiler::Profiler p("UseInserter()");
string s;
s << "Int: " << 42
<< ", bool: " << false
<< ", float: " << 4.2
<< ", string: " << str
<< ", char: " << '_';
return s;
}
string UseToString() {
// profiler::Profiler p("UseToString()");
string s = "Int: " + toString(42)
+ ", bool: " + toString(false)
+ ", float: " + toString(4.2)
+ ", string: " + str
+ ", char: " + '_';
return s;
}
string UseSStream() {
// profiler::Profiler p("UseSStream()");
ostringstream ss;
ss << "Int: " << 42
<< ", bool: " << false
<< ", float: " << 4.2
<< ", string: " << str
<< ", char: " << '_';
return ss.str();
}
int main() {
for (int i = 0; i < 1000001; ++i) {
UseInserter();
UseToString();
UseSStream();
}
// profiler::report(cout);
return 0;
}
-------
(I used my homemade profiler as posted else-thread here on clc++, I
commented out its use to ease using professional tools on your side)
...and here is its output:
-------
Id: UseInserter()
Avg time: 0.016252 msecs
Total time: 16252 msecs
Calls count: 1000001
Id: UseSStream()
Avg time: 0.00664999 msecs
Total time: 6650 msecs
Calls count: 1000001
Id: UseToString()
Avg time: 0.016495 msecs
Total time: 16495 msecs
Calls count: 1000001
As you can see even from this test, value based concatenation (your UseToString)
is generally slowest.
But without adding << functionality, value based concatenation is all that you
have for std::string in an expression.
Cheers & hth.,
- Alf