Using printf in C++

L

Luca Risolia

That doesn't preclude someone from learning C++ and its idioms in a few days. The first
language is hard, the rest come easy. OJT happens more often than you think
and it is a foolish architect who assumes otherwise.

Are you kidding, right? It takes few YEARS for a C programmer to learn
and fully take advantage of C++ in the most profitable way. One year is
the time required to remove the temptation to do everything the C-way.
The effort to learn is worth it, since C++ (compared to C) makes sense
only if you care to understand all the idioms, design patterns,
programming paradigms the language supports, together with the standard
library.
 
I

Ian Collins

And how much production code have you written? Large projects?

Rather a lot over the past 30 years!
Have you ever worked at a company?

I've both worked in and managed development teams and engineering
departments.
This happens all the time.

Not under my watch.
They don't
hire a perl programmer to maintain a 30 line perl script; even most of
my RTL designers write perl. These are guys who design processors for a living
and use SystemC and other simulation environments, but certainly can spend
the few hours needed to learn something new.


That doesn't preclude someone from learning C++ and its idioms in a few days.

You jest?
The first
language is hard, the rest come easy. OJT happens more often than you think
and it is a foolish architect who assumes otherwise.

So what, you write your code the the lowest common denominator of any
language programmer can maintain it? The worst C++ code tends to be
written by C programmers who know enough C++ syntax to be dangerous
without understanding the language a a whole. The same can probably be
said for C code a Fortran programmers.
 
G

Guest

I agree with Ian Collins. This is madness.

what world do you live in? I mostly code in C++. The guy at the next desk codes in C (he's an embedded guy). I don't treat him like a moron.

AOL. me too.

me, for one.
And how much production code have you written?

quite a bit
Large projects?

define "large". Previous project 750 KLOC, current 400 KLOC. Multi-year development.
Have you ever worked at a company? This happens all the
time.

which probably explains why much Perl is shit. Do most Perl "programmers" even know Perl has functions?
They don't
hire a perl programmer to maintain a 30 line perl script;

probably reasonable for 30 lines. (Though you can do an awful lot in 30 lines of Perl...). But if it's a significant application it would pay to buy (or grow) some Perl experience.
even most of my RTL designers write perl. These are guys who
design processors for a living and use SystemC and other
simulation environments, but certainly can spend the few hours > needed to learn something new.

you ain't gonna learn Perl in a few hours. I'm a mediocre Perl programmer and I've invested more than a few hours.

very. Exceptions, templates, STL, that whole OO thingy...
That doesn't preclude someone from learning C++ and its idioms in a few days.

bollocks. There's lots of people who call themselves C++ programmers that can write C++ that compiles and runs. But look inside and it's C. Little useof the STL. Mounds of procedural code stuffed into God Antipatterns. No exception safety. "What's exception safety?"
The first language is hard, the rest come easy.

I'm trying to learn Haskell at the moment. I'm not finding it easy! And itsmy Nth programming lanaguage.

The hard bit about C++ (IMHO) is OOP (and OOD) rather than syntax.
OJT happens more often than you think
and it is a foolish architect who assumes otherwise.

why bother using C++ at all if you cripple it for under-trained maintenabnce programmers?
 
B

BGB

I agree with Ian Collins. This is madness.

"Madness?! This! Is! c.l.c++!!!"


jumping in here, as some of this seemed interesting...

what world do you live in? I mostly code in C++. The guy at the next desk codes in C (he's an embedded guy). I don't treat him like a moron.

well, to be fair, in my case people have been condescending towards me
using a lot of C (though, most of this has been from Java and C#
developers).


a lot has been of the sort of thing like.

a lot has been defense, like "yeah, in fact you *can* do OO stuff in C,
and it isn't even really all that difficult". well, along with defending
that it is also possible to do GUI / network / multithreaded / ... code
in C as well, and use a GC (Garbage Collector) if so desired, ...

apparently a lot of stuff I am doing is "impossible" according to some
people, grr...


but, in a way, given that C is a language where pretty much nothing
really comes out looking pretty, how pretty it is to use a feature (or
how much of a hack it is to implement it) isn't really a good measure of
how possible it is to use a feature.

this is, after all, part of why people build abstractions.

AOL. me too.


me, for one.

I am primarily a C programmer who also does some C++, if this counts...


idiomatic? probably not.
apparently my C code probably isn't terribly idiomatic either.

(someone recently used the term "hopelessly strange" to describe it...).
I guess another person had described it as "down the rabbit hole".

but, oh well, whatever...

quite a bit


define "large". Previous project 750 KLOC, current 400 KLOC. Multi-year development.

my current project is around 1 Mloc, and the codebase has (more or less)
been in development / in-use for around 12 years.


note that, for the most part, there has been no particular "end-goal" or
up-front design, so the codebase is more of an amorphous multi-tool
(with smaller sub-goals or use-cases).

then some people complain that such a project is never "completed", but
the thing is, projects of this sort never are "complete" in the
traditional sense (only particular features get "completed", or
something can be "reasonably complete" WRT a particular use-case).

and, for the most part, its goal hasn't been "to make money" or similar,
rather it has been more like "giving me something to do" (with money
more as a side thing: one needs money to live off of and to continue
doing what one is doing).

note that "changing the world" hasn't really been much of a goal either.
(as I see it, the world goes on either way... I just try to live my life
and do my thing, ...).

which probably explains why much Perl is shit. Do most Perl "programmers" even know Perl has functions?

who knows sometimes?...

probably reasonable for 30 lines. (Though you can do an awful lot in 30 lines of Perl...). But if it's a significant application it would pay to buy (or grow) some Perl experience.

my case: almost no personal experience with Perl.

you ain't gonna learn Perl in a few hours. I'm a mediocre Perl programmer and I've invested more than a few hours.

yep.

in my case, it takes a while to really learn new things all that well
(and apparently I am fairly mediocre intelligence-wise), so I mostly
prefer things which are tried and true.

very. Exceptions, templates, STL, that whole OO thingy...

well, OO can sort of be done in C.

fair enough for the rest.

bollocks. There's lots of people who call themselves C++ programmers that can write C++ that compiles and runs. But look inside and it's C. Little use of the STL. Mounds of procedural code stuffed into God Antipatterns. No exception safety. "What's exception safety?"

yeah.

can probably describe most of what little C++ code I have written thus
far. it mostly wasn't meant to be idiomatic though, more because in this
case I had considered using a library which itself presented a C++ only
interface. I ended up not using the library, but the code in question
remained as-is.

note that the code in question presents a C friendly external interface,
and the tools in use with it were modified to accept the C++ subset in
use (basically, excluding the use of namespaces).

I'm trying to learn Haskell at the moment. I'm not finding it easy! And its my Nth programming lanaguage.

my experience with Haskell:
looked at it some;
tried to mess with it some;
gave up never to look at it again...

The hard bit about C++ (IMHO) is OOP (and OOD) rather than syntax.

possibly, or at least this a bigger mental jump.

I don't think that OOP vs non-OOP is really a matter of C vs C++, since
technically, OOP can be done in C (pretty or not), and non-OOP code can
be written in C++.

a person can also write procedural style code in Java as well.
 
P

Pavel

Leigh said:
On 21/05/2012 05:11, Pavel wrote:
[snip]
Well, let me give you an example of Symbian (proudly listed by the [The
Programming Languages
Beacon](http://www.lextrait.com/vincent/implementations.html) cited
above by Martin B. as written in C++).

Being familiar with the system quite intimately in the past, I can
attest that, true, Symbian (originally, EPOC32) was a rare example of an
operating system (very well-performing at that), almost entirely written
in C++, BUT:

They did not use standard C++ exceptions in the OS. At that, they badly
wanted to use RAII (and, consequently, exceptions) and they achieved
their noble goal by creating (quite an ugly) way requiring client code
cooperation via so called "clean-up stack". That is, for the objects
that you wanted to be RAII and exception-safe, you would create an
object with a (throwing, if I recall it correctly) operator new in
dynamic memory and manually put it onto so called "cleanup stack". In
case your code did not throw, you would need to clean up your cleanup
stack manually; otherwise, runtime would take care of it. You could NOT
create an exception-safe "auto" RAII object -- quite a divergence from
the standard C++ RAII implementation practice, isn't it?

(They did not use templates either but I am not sure about the reason
for that -- probably the templates were not mature enough yet in a
low-common-denominator of MSVC (that they used to build emulator
binaries) and g++ (that they used to build production binaries) -- but
exceptions certainly were)

Their excuse for the "non-standard exception" mess? -- performance
concerns for using standard C++ exceptions. I tend to believe them
because the guys obviously knew everything there is to know about the
design-for-performance and then some (I am judging by the end result of
their effort: at ~1998 they were selling products built on a rock-solid
hard real-time system with preemptive multitasking, extensive support
for priorities, order-of-tens-microsecond response times for app
communicating via serial and IRDA ports and perfectly responsive nice
half-VGA-size pen-interface GUI -- all powered by a poor little 7-MHz
ARM CPU. I have yet to see a better-performing RTOS -- and uglier RTOS
API).

The later versions of Symbian OS did support C++ exceptions.
Yes, their target hardware became much faster starting with Strong ARM so they
thought they could afford these. They are dying though (Nokia seems to be moving
onto Windows 8 (C-written kernel?)) and nothing much will have left on Symbian
after that).

I do not think performance is immediately responsible for Symbian's depature;
but we will never know now. It certainly was their major selling point when they
were on a rise though (before adding exceptions etc).
The reason Symbian eschewed templates was to keep executable sizes down (I'm not
sure if this was also thrown out as an idiom in later versions of Symbian OS as
handset hardware specifications improved; I believe the STL *was* finally
embraced).

/Leigh

-Pavel
 
P

Pavel

Ian said:
Well it does half the job. But it fails when the thing you are streaming to
isn't an ostream, such as some form of binary stream.
Then you do not need iostream. You are talking about overloading here, not
really templates. I used more than one system that used operator<< overload to
output to binary stream; and even wrote some (which I would not do now) and I
know it first hand that the generality here is only seeming: imagine you
compile-time-parameterized with stream type some algorithm that was originally
written for iostream and includes something like

destStream << dec << someInt << ' ' << setfill('0') << someOtherInt ...

If you replace destStream with binary stream all those manipulators (and
separating entries with the space, BTW) won't make any sense. Implementing them
with empty bodies just to keep compiler happy will not compensate for misleading
a human code reader.

Can you give an example of a practically useful code using essential features of
iostream that could be parameterized and used without change with a binary stream?

If you just like the now-idiomatic use of << for output -- it's a separate
story, but again it's just overloading and the existence of iostream does not
add much to it (except for the idiom) as compared to, for example, overloading
another operator or a function.

-Pavel
 
B

BGB

Yes. I'm very tempted to start trying to refute claims from Pavel and
S.L. in this thread ... but then I do a reality check. Templates
(writing new ones, and especially using the standard library's
containers and algorithms) are one of the cornerstones of modern C++.

Put differently, if I want C, I know where to find it.

FWIW, even in C, there is a "sort-of" analogue:
multi-line macros.

for example (grabbing something from some of my own code):
#define BVT_JMPCC_BODY(ty, cc) \
ty *u, *v; \
if(cur->data) \
{ \
BVT_PopXY_Ty(ty, u, v); \
if((*u)cc(*v)) \
{ \
BVT_FreeUV_Ty(ty, u, v); \
return((BSVM_ThreadOp *)(cur->data)); \
} \
BVT_FreeUV_Ty(ty, u, v); \
return(cur->next); \
} \
cur->data=(void *)BSVM_Thread_GetOpJumpTarget(ctx, cur, cur->i); \
if(cur->data) { return(cur->fcn(ctx, cur)); } \
else { *(int *)-1=-1; } \
return(cur->next)


note that this macro in turn invokes several other macros, ...
and contains a self-destruct mechanism...


in this case it was being used fairly effectively, and the logic would
likely have been repeated either via a macro or via copy/paste anyways
(macros ended up used frequently in the region of code in question).


great fun...
 
I

Ian Collins

Then you do not need iostream. You are talking about overloading here, not
really templates. I used more than one system that used operator<< overload to
output to binary stream; and even wrote some (which I would not do now) and I
know it first hand that the generality here is only seeming: imagine you
compile-time-parameterized with stream type some algorithm that was originally
written for iostream and includes something like

destStream<< dec<< someInt<< ' '<< setfill('0')<< someOtherInt ...

If you replace destStream with binary stream all those manipulators (and
separating entries with the space, BTW) won't make any sense. Implementing them
with empty bodies just to keep compiler happy will not compensate for misleading
a human code reader.

I wouldn't. They would be in the output operator for an ostream. The
output operator for a binary stream would be different.
Can you give an example of a practically useful code using essential features of
iostream that could be parameterized and used without change with a binary stream?

Yes. I have a library that manipulates LDAP data. The processed data
can be output in several forms: to an ostream it gets written in LDIF
format, to another process the data is written as JSON and two another
stage of processing, it is output raw. So I have some template methods
similar to

template <typename Stream> void outputRootEntries( Stream& out ) const {
out << findEntry(Dn::addDcTo("o=staff"));
...
}

Where Stream can be anything an Entry object has a matching output
operator for.
 
I

Ian Collins

FWIW, even in C, there is a "sort-of" analogue:
multi-line macros.

for example (grabbing something from some of my own code):
#define BVT_JMPCC_BODY(ty, cc) \
ty *u, *v; \
if(cur->data) \
{ \
BVT_PopXY_Ty(ty, u, v); \
if((*u)cc(*v)) \
{ \
BVT_FreeUV_Ty(ty, u, v); \
return((BSVM_ThreadOp *)(cur->data)); \
} \
BVT_FreeUV_Ty(ty, u, v); \
return(cur->next); \
} \
cur->data=(void *)BSVM_Thread_GetOpJumpTarget(ctx, cur, cur->i); \
if(cur->data) { return(cur->fcn(ctx, cur)); } \
else { *(int *)-1=-1; } \
return(cur->next)


note that this macro in turn invokes several other macros, ...
and contains a self-destruct mechanism...


in this case it was being used fairly effectively, and the logic would
likely have been repeated either via a macro or via copy/paste anyways
(macros ended up used frequently in the region of code in question).

great fun...

Also a great way to bloat the code!

With function templates the compiler can choose whether or not to inline
them. With macros, there isn't any choice to be made.
 
P

Pavel

Luca said:
Luca Risolia said:
On 22/05/2012 21:56, jacob navia wrote:
Sure, then the boss says:

"I want rounding to 2 decimals, names flush left in the first
column that must be 16 chars long"

printf("%-16s|%10.2g|\n",name,value);


And how would it look in C++ please?

In C++ it would look like:

cout<< format<< name<< value<< '\n';


No, you're not counting the OStreamWrapper you need to define.


I am. This is C++.
So, 33 lines to replace one. I don't see how that is a win.

It's a win because you make the code more readable, flexible and type-safe.

IMHO It's only a win if the programmer is payed per LOC -- and only for him or her.
It's
a win when you want to share the same code and logic with others; you define the
manipulator once and place it in a library for re-use in big projects,

-Pavel
 
P

Pavel

Jorgen said:
Jorgen said:
I really like 'printf' (i.e., I got used to it) and I'm not liking
'cout' that much.

Well, the iostream library is one of the very first C++ libraries. I
would call it broken by design. The fumbling with the shift operators
primarily makes the code unreadable,

IME it makes the code /readable/, once you deal with the types which
printf() can't print.

const FooClient& c = ...
const in6_addr& addr = ...

os<< "Client"<< c<< " connected from"<< addr<< '\n';

compared to

char buf[INET6_ADDRSIZE];
std::fprintf(f, "Client %s connected from %s\n",
to_string(c).c_str(),
inet_ntop(INET6_ADDR, (void*)addr, buf, sizeof buf));
This is not apples-to-apples: you obviously used user-defined operator<< in your
C++ code.

Yes -- the kind of thing you do *once* per type in your program, close
to the definition of the type (or in this case, in some "my socket C++
stuff" file). Then it works automatically in the hundreds or
thousands of places where it's used.

That's why I felt I didn't have to add the definition of os<< addr
to the "cost" of the printout.
C code then should be allowed to use print_in6_addr(FILE*, in6_addr*)
and print_foo_client(FILE*, FooClient*) functions. With these, C code would be
more readable than C++ even though slightly wordier.

We're not talking C though, are we? I assumed we talked iostreams
versus printf in C++; my fprintf example is clearly not C.

I also really don't see how a print_in6_addr(FILE*, const in6_addr&)
would help. Do you find this readable?

std::fputs("Client ", f);
print_foo_client(f, c);
std::fputs(" connected from ", f);
print_in6_addr(f, addr);
std::fputs("\n", f);
still not apples to apples. You assumed 'using' in your C++ (iostream) code and
diligently put std:: in my supposed code. BTW, fprintf with a single argument is
implemented as fputs even in compile time in gcc (and, I believe, not only), so
the real thing to compare with is:

fprintf(f, "Client ");
print_foo_client(f, c);
fprintf(f, " connected from ");
print_in6_addr(f, addr);
fprintf(f, "\n");

As promised, it's wordier but not less readable than the ostream-based code.

Oh, BTW, if you happen to forget to include one of your headers declaring one of
your custom-made operator<<, good luck with sifting through hundreds lines of
the error message listing different "candidate" overloads (I assume you will
have a few hundreds if it's your policy to override operator<< and your software
is of meaningful size so it has some few hundred classes to output) -- all to
get which one exactly it was. For every "candidate" operator, the compiler will
have to diligently name the type of its left-hand-side argument -- and, because
it is an instantiation of a template with multiple arguments, some of which are
in turn template instantiations, every such inclusion of the "full name" of
ostream will take way beyond 100 characters for your enjoyment.

If, on the other hand, you forgot to include print_foo_client header for my code
above, there will be a single-line compiler error with the missing function name
right there at it.

Happy streaming.
-Pavel
 
P

Pavel

Ian said:
I wouldn't. They would be in the output operator for an ostream. The output
operator for a binary stream would be different.


Yes. I have a library that manipulates LDAP data. The processed data can be
output in several forms: to an ostream it gets written in LDIF format, to
another process the data is written as JSON and two another stage of processing,
it is output raw. So I have some template methods similar to

template <typename Stream> void outputRootEntries( Stream& out ) const {
out << findEntry(Dn::addDcTo("o=staff"));
..
}

Where Stream can be anything an Entry object has a matching output operator for.
As I said, if all you want is to use << idiom (which some people love, some
people hate and I am largely indifferent to except for the cases when I am
getting an error message with candidates on 10 to 1000 screens in which case I
tend to be more on the hating side) -- you can do it; but in this case there is
nothing iostreamish about it (you could as well use operator, or a function with
the name 'o' or 'out' or whatever). <iostream>'s goal was to provide formatting
flags, options and manipulators -- and these were specific to iostreams. Even
though you can extend these (even add new flags), you cannot (and probably don't
want) to share names with the old flags/manipulators for, say, binary output
"options" (think of compressed-vs-uncompressed, variable-vs-fixed length options
etc). As soon as you use one of those formatting features (which I referred to
as "essential features of iostream" above -- I admit I could have emphasized it
better), your code for iostream will stop making sense for binary stream and
vice versa. If you don't use any of these, a comparison to printf does not make
sense because convenient format specifiers is pretty much all a printf-like
function delivers (it does only one thing, but well).

-Pavel
 
I

Ian Collins

As I said, if all you want is to use<< idiom (which some people love, some
people hate and I am largely indifferent to except for the cases when I am
getting an error message with candidates on 10 to 1000 screens in which case I
tend to be more on the hating side) -- you can do it; but in this case there is
nothing iostreamish about it (you could as well use operator, or a function with
the name 'o' or 'out' or whatever).

The code originally started of just outputting to a file. When the
requirements grew, the easiest way to support them was to change the
concrete output functions to template functions. Hence my original
point which was using a toString() member is now as flexible as
overloading operator <<. Unless you make things even more clunky and
add the stream as a parameter to toString() as it is for the operator.
<iostream>'s goal was to provide formatting
flags, options and manipulators -- and these were specific to iostreams.

Not really, I seldom use them. But I do use iostream to serialise user
defined types in the same way as built in ones, which is a fundamental
gaol of C++.
Even
though you can extend these (even add new flags), you cannot (and probably don't
want) to share names with the old flags/manipulators for, say, binary output
"options" (think of compressed-vs-uncompressed, variable-vs-fixed length options
etc). As soon as you use one of those formatting features (which I referred to
as "essential features of iostream" above -- I admit I could have emphasized it
better), your code for iostream will stop making sense for binary stream and
vice versa. If you don't use any of these, a comparison to printf does not make
sense because convenient format specifiers is pretty much all a printf-like
function delivers (it does only one thing, but well).

It will, but you have to write a new operator for the new type. The
fact that you can (and I often have) then use this operator in a
template was the point I was trying to make.
 
J

Jorgen Grahn

Jorgen said:
Jorgen Grahn wrote:
I really like 'printf' (i.e., I got used to it) and I'm not liking
'cout' that much.

Well, the iostream library is one of the very first C++ libraries. I
would call it broken by design. The fumbling with the shift operators
primarily makes the code unreadable,

IME it makes the code /readable/, once you deal with the types which
printf() can't print.

const FooClient& c = ...
const in6_addr& addr = ...

os<< "Client"<< c<< " connected from"<< addr<< '\n';

compared to

char buf[INET6_ADDRSIZE];
std::fprintf(f, "Client %s connected from %s\n",
to_string(c).c_str(),
inet_ntop(INET6_ADDR, (void*)addr, buf, sizeof buf));
This is not apples-to-apples: you obviously used user-defined operator<< in your
C++ code.

Yes -- the kind of thing you do *once* per type in your program, close
to the definition of the type (or in this case, in some "my socket C++
stuff" file). Then it works automatically in the hundreds or
thousands of places where it's used.

That's why I felt I didn't have to add the definition of os<< addr
to the "cost" of the printout.
C code then should be allowed to use print_in6_addr(FILE*, in6_addr*)
and print_foo_client(FILE*, FooClient*) functions. With these, C code would be
more readable than C++ even though slightly wordier.

We're not talking C though, are we? I assumed we talked iostreams
versus printf in C++; my fprintf example is clearly not C.

I also really don't see how a print_in6_addr(FILE*, const in6_addr&)
would help. Do you find this readable?

std::fputs("Client ", f);
print_foo_client(f, c);
std::fputs(" connected from ", f);
print_in6_addr(f, addr);
std::fputs("\n", f);
still not apples to apples. You assumed 'using' in your C++ (iostream) code and
diligently put std:: in my supposed code.

Did I? Where?
BTW, fprintf with a single argument is
implemented as fputs even in compile time in gcc (and, I believe, not only), so
the real thing to compare with is:

fprintf(f, "Client ");
print_foo_client(f, c);
fprintf(f, " connected from ");
print_in6_addr(f, addr);
fprintf(f, "\n");

Sure, but I'm reluctant to use fprintf() with something which isn't a
format string as its second argument, because of this pitfall

fprintf(f, "Percent (%) of the cost:\n");

Good compilers give warnings for this, but still ...
As promised, it's wordier but not less readable than the ostream-based code.

I find it less readable. Your newsreader (or something) messed up the
spacing in my code; perhaps that's part of the reason you disagree.
Or perhaps we just don't agree about what's readable.
Oh, BTW, if you happen to forget to include one of your headers declaring one of
your custom-made operator<<, good luck with sifting through hundreds lines of
the error message listing different "candidate" overloads

No sifting needed; I just have to scroll up to the first one to see
what's going on. Not ideal, but that's life if you use C++, at least
with gcc. It's not specific to iostreams.

/Jorgen
 
B

BGB

Also a great way to bloat the code!

yeah, but in this case it was likely inescapable.

the macros were mostly needed for adding a lot of "statically typed"
logic to my script VM / interpreter, which replaced a lot of slower
dynamically-typed execution paths.


the logic above was actually for a lower-performance variety of jumps,
there was a faster variety, but it involved 2 functions per jump (to
eliminate the first "if()", one function would replace itself by the
other). in this case, leaving an unnecessary 'if' in the execution path
was seen as acceptable, since the branch predictor usually got it right
in this case (the 'if' body would always execute except the for the
first time a given conditional jump was seen in a given block).

(the faster version was used for unconditional jumps).


not that all of the code is written this way (it is actually fairly
uncommon in-general).

With function templates the compiler can choose whether or not to inline
them. With macros, there isn't any choice to be made.

yes, however:
this code was C, and C doesn't have templates;
they were being invoked specifically (so, it was fairly deliberate that
they would be inlined);
....
 
P

Pavel

Jorgen said:
Jorgen said:
I really like 'printf' (i.e., I got used to it) and I'm not liking
'cout' that much.

Well, the iostream library is one of the very first C++ libraries. I
would call it broken by design. The fumbling with the shift operators
primarily makes the code unreadable,

IME it makes the code /readable/, once you deal with the types which
printf() can't print.

const FooClient& c = ...
const in6_addr& addr = ...

os<< "Client"<< c<< " connected from"<< addr<< '\n';

compared to

char buf[INET6_ADDRSIZE];
std::fprintf(f, "Client %s connected from %s\n",
to_string(c).c_str(),
inet_ntop(INET6_ADDR, (void*)addr, buf, sizeof buf));

This is not apples-to-apples: you obviously used user-defined operator<< in your
C++ code.

Yes -- the kind of thing you do *once* per type in your program, close
to the definition of the type (or in this case, in some "my socket C++
stuff" file). Then it works automatically in the hundreds or
thousands of places where it's used.

That's why I felt I didn't have to add the definition of os<< addr
to the "cost" of the printout.

C code then should be allowed to use print_in6_addr(FILE*, in6_addr*)
and print_foo_client(FILE*, FooClient*) functions. With these, C code would be
more readable than C++ even though slightly wordier.

We're not talking C though, are we? I assumed we talked iostreams
versus printf in C++; my fprintf example is clearly not C.

I also really don't see how a print_in6_addr(FILE*, const in6_addr&)
would help. Do you find this readable?

std::fputs("Client ", f);
print_foo_client(f, c);
std::fputs(" connected from ", f);
print_in6_addr(f, addr);
std::fputs("\n", f);
still not apples to apples. You assumed 'using' in your C++ (iostream) code and
diligently put std:: in my supposed code.

Did I? Where?
BTW, fprintf with a single argument is
implemented as fputs even in compile time in gcc (and, I believe, not only), so
the real thing to compare with is:

fprintf(f, "Client ");
print_foo_client(f, c);
fprintf(f, " connected from ");
print_in6_addr(f, addr);
fprintf(f, "\n");

Sure, but I'm reluctant to use fprintf() with something which isn't a
format string as its second argument, because of this pitfall

fprintf(f, "Percent (%) of the cost:\n");

Good compilers give warnings for this, but still ...
As promised, it's wordier but not less readable than the ostream-based code.

I find it less readable. Your newsreader (or something) messed up the
spacing in my code; perhaps that's part of the reason you disagree.
Or perhaps we just don't agree about what's readable.
Oh, BTW, if you happen to forget to include one of your headers declaring one of
your custom-made operator<<, good luck with sifting through hundreds lines of
the error message listing different "candidate" overloads

No sifting needed; I just have to scroll up to the first one to see
what's going on. Not ideal, but that's life if you use C++, at least
with gcc. It's not specific to iostreams.
I disagree. My snippet, while staying valid C++ and gcc code, does not have this
issue.


-Pavel
 
P

Pavel

Ian said:
The code originally started of just outputting to a file. When the requirements
grew, the easiest way to support them was to change the concrete output
functions to template functions. Hence my original point which was using a
toString() member is now as flexible as overloading operator <<. Unless you make
things even more clunky and add the stream as a parameter to toString() as it is
for the operator.
Isn't it exactly the fputs with generalized notion of FILE* (or fprintf with no
format specifiers, if you prefer your FILE* argument first)? It may be wordier
than it has to be, due to the absense of chaining facility; but reading is
sometimes even simper; and you will be thankful for putting every "insertion" on
the line of its own if you receive a compiler error?

toString approach is bad from both performance and flexibility viewpoints. It's
main advantage is the simplicity of the paradigm: all you need to do to make
your object toString-able is to select a sensible string representation of it
and code it. Due to the absense of java-String-like concatenation functionality
in Standard C++ lib, istringstream is the natural choice for implementing it
(you have to type some extra stuff as compared to Java but overall tolerable).

This simplicity is what creates a flexibility problem: user cannot control how
an object is put into stream; also there is no notion of a "transaction" or
doing something automatically before inserting first and after inserting last
argument (which fprintf-like functions can do without no issue; but more C++ish
approach is also possible). These are of course the extensions to your
requirements of a simple template-able per-object insertion -- and iostreams
could address them both; but they don't: every insertion in the expression is on
its own.

boost format seems to fix this -- for price of performance and supporting a
pseudo-Python's (?) idiom of formatting with %; I still wonder why no-one does
operator, as it would give most concise syntax (commas are naturally glued to
the left argument so one space down) and, as an additional bonus, would have no
issues with precedence (boost::format doc warns against things like format(...)
% x + y; format(..), x + y would work just fine (i.e. as expected)).
Not really, I seldom use them. But I do use iostream to serialise user defined
types in the same way as built in ones, which is a fundamental gaol of C++.

I do, too; it's an idiom and fighting it is like spitting against the wind.
There are some hiccups in the iostream's design even for this simple task though:

a. It's too wordy and heavier on eye than it had to be. As I said above, comma
would work much better (I think this is one of the reasons the likes of printf
are so popular). E.g. compare

out, a, b, c;

or more pytonish

out% a, b, c;

to

out << a << b << c;

((out a b c) would be even better but we have to stay in C++ land :) ).

b. It defines operator<< as an ostream member function for some fundamental
types and as a free-standing function for other fundamental types and
user-defined types. The result is often unexpected. Below is a sample (but IMHO
not very simple) puzzle:

-----cut here----
#include <iostream>
#include <sstream>
using namespace std;

#define MK_STR(args) (static_cast<ostringstream&>(ostringstream() \
<< args)).str()

int
main(int, char*[])
{
static const char *someText = "abc";
cout << "cout: " << "someText: " << someText << endl;
string msg(MK_STR("MK_STR: " << "someText: " << someText));
cout << msg << endl;
return 0;
}
-----cut here----

A programmer wanted to define a macro for concatenating objects into strings for
not-performance-critical applications. S/he expected to get the output like this:

cout: someText: abc
MK_STR: someText: abc

Instead, s/he sees something like this:

cout: someText: abc
0x400e12someText: abc

Care to explain what's happening and change MK_STR definition to produce the
expected result? (My answer is in the PS to this letter).
It will, but you have to write a new operator for the new type. The fact that
you can (and I often have) then use this operator in a template was the point I
was trying to make.
The point is taken.

-Pavel

PS: (the reason for the above behavior is that the temporary is an object and
the best match for inserting const char* is a member function accepting const
void* as an argument (only one standard conversion of const char* to const void*
if compiler considers << a member-function; and there is no standard conversion
from a temporary object to a non-const ref needed to use a free-standing
function); the hackish fix is to redefine MK_STR() as

#define MK_STR(args) (static_cast<ostringstream&>(ostringstream() << flush \
<< args)).str()

The return type of do-nothing ostringstream() << flush is a non-const reference;
then the best match is a free-standing operator<< (no conversions needed).
 

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,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top