From: Giovanni Bajo (giovannibajo_at_[hidden])
Date: 2003-04-21 21:27:35
there are several aspects of boost::format which I don't like. Due to work
issues, I managed to completely miss the review. I had a parallel proposal
back in time, but I didn't have time to push it enough. I guess I can start
a discussion about what I don't like, so that maybe people can explain me
why I'm wrong.
- Why is there a need for a format object? The documentation fails to show a
real-world example where this gives some advantages. Not only that, but the
format object is also very big:
boost::format fmt = boost::format("%04d %08x %s");
std::cout << sizeof(fmt) << std::endl;
This prints 228, and there might be other data allocated on the heap.
This looks a big flaw in the design to me, since I don't see any real-world
reason why you would need to use this.
- It's not default convertible to a string. The most common usage, by far,
is simply to print the value of a standard type (integer, string) within
another string, and you need to do string -> format -> io::str to get the
result. And to convert to a string, you have two choices, and both of them
happen to be unfortunate:
1) you can use the member str() function. (boost::format("%04d %04x") % 123
% 456).str(). So ugly.
2) you can use the free-function str(), but it happens to have been defined
in a different namespace. So you need to write
boost::io::str(boost::format("%04d %04x") % 123 % 456)), where half of the
line is what I actually need (string formatting). Of course, you can inject
namespaces, but this is unfortunate as well, since a format library is
something which is not very isolated, you probably need it everywhere. If I
use Boost.Spirit, I can inject the namespace in the places where I write the
parser. But a format library? You need it everywhere.
Besides, defining str() in another namespace totally kills Koenig lookup.
str(boost::format("%04x") % 123) would have worked if str was defined inside
namespace boost, and it would have been a
decent compromise. Alternatively, boost::format could have been
boost::format_ns::format and then "using format_ns::format" inside boost,
which stills allows Koenig lookup and doesn't force you to define a symbol
"str" in namespace boost.
- To convert boost::format to a string, you need to format all the
you get an exception. Of course, you can disable this behaviour, but you
have to do it for every single format class you create. Or you can define a
function my_fmter() like explained in the documentation. which simply create
and then pass by value an object of 228 bytes which probably invokes chains
of operator= for subobjects. Not very efficient, isn't it? I'm just trying
to write "My name is %s" % "Giovanni", and I already need to go through
several copy constructors, assignments, big objects and functions (in
different namespaces) to convert back to a string. Result: I use string("My
age is ") + ltoa(23) + string(", baby") because it's handier. This exception
have been a template policy (if needed at all! I'm totally contrary to it,
as a start).
- Don't ever try to use string conversion on a temporary object returned by
a function, you get a segfault. Sorry, I didn't have time to track down the
bug. I realize I'm unfair here since every program can have some bugs, but
complexity is _the_ issue with Boost.Format, and usually complexity leads to
a higher likeness of bugs.
- GCC 2.95 is killed because of the use of char traits. Not the most
important problem of course, but hey: how many people are willing to drop
support for any compiler to use a format library? They'd go for sprintf()
instead, and keep compiler support. And such a complex code is surely harder
to keep compatible with broken/incomplete compilers.
Let me resume it all: boost::format() is _too_ complex. It's a library meant
to solve a very small, easy, "everyday" problem, and it offers rarely-used
advanced functions, a very complex implementation (my own string format
library is probably 10 times smaller: of course it's not as advanced as
boost::format, but I reckon it's not missing basic features at least), and
performance prices to pay even for people that just need to print a number
within a string.
By comparison, boost::bind() solves a problem which is order of magnitude
more complex, and offers such a clean and easy interface that it's almost
unbelieavable. Boost.Format, in projection, looks like the
std::bind1st/bind2nd/memfun interfaces. They can be used to work out
something, but many people just preferred to avoid them (and <algorithm>).
Now, let's see my counter proposal. It's the library I'm already using. I
also modified it to make it compatible with Boost.Format interface and
format specifiers to be sure I was not missing something big in it.
- String formatting is a need in everyday code. And it's a basic need: you
usually want to print integers/strings formatted within strings. You
advanced formatting options like %-10d, let alone weird stuff like tab stops
or manipulators (people that like printf() usually it's because they don't
need manipulators to specify "print this in hex"). In C++, you also want
positional arguments, since it's typesafe. I'm not against adding advanced
formatting options, but the library should be tuned for easy uses, which
means easy and direct interface.
- No class boost::format. No function boost::format. It's just template
<typename T> std::string operator%(std::string, T whatever), and you inject
it into your global namespace (a clash is _very_, _very_ unlikely). That's
all you need: a function that lets you format a single parameter into a
string and get the new string as result. In a perfect world, I'd like that
operator% to be defined into namespace std.
- Notice that you don't *even* need to convert to string explicitally most
of the times:
void Error(std::string msg);
Error("Sorry, there are only %d apples in the basket" % numApples);
Because it gets default-converted! The same code with Boost.Format is:
"Sorry, there are only %d apples in the basket") % numApples
It gets a bit better if you inject everything, but hey, not everybody can
afford it. And I don't dare measure the performance difference. I realize it
should not be timecritical, but why wasting cycles for _NO_ clear advantages
in the syntax or semantic?
- This default conversion (syntactic sugar) does not happen when using
streams, like in:
std::cout << "%d" % 3 << std::endl; // doesn't compile
std::cout << std::string("%d") % 3 << std::endl; // ok
But if you really want, you can do:
void cout(std::string msg)
std::cout << msg;
cout("%d\n" % 3);
Which works for me. Notice that you're not losing semantic here, since
internally the implementation still uses std::stringstream.operator<<
to format the parameter, so you just need an overload for operator<< as
usual for your custom object formatting.
And I guess that the simple version (cout << "%d" % 3 << endl) could be made
to work as-is eventually, with some overload magic,
if anybody cared of it enough. Maybe you just need a non-template
operator<<(ostream, string) in your namespace.
- For purists hating operator%, you can have a boost::format, but it's just
a wrapper for a std::string, constructible from string and default
convertible to string, and it has a member function arg() to format a
parameter. This is modeled against QT's QString.
That's all. What else do you need? I mean, do you _really_ need?
My implementation is very light, and can be compiled on almost all the
compilers around. It uses Spirit to parse the format specifiers, so the code
is small. To be fair, it's hardcoded for std::string since I don't need
wchar_t stuff, but I don't see adding this as a big problem. It does not
advanced formatting or complex stuff, but I can't see a problem in adding
additional features, and they are not going to change the flexibility and
immediatness of use.
I'd like to know your opinions on this. I think I have some good points to
propose a radical change to Boost.Format.
I also hope this mail didn't come out too rude, I don't mean to offend
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk